mod_rendezvous_apple.c [plain text]
#define CORE_PRIVATE 1
#define MSG_PREFIX "mod_rendezvous_apple:"
#include <CoreServices/CoreServices.h>
#include <SystemConfiguration/SystemConfiguration.h>
#include <DNSServiceDiscovery/DNSServiceDiscovery.h>
#include <DirectoryService/DirServices.h>
#include <DirectoryService/DirServicesUtils.h>
#include <DirectoryService/DirServicesConst.h>
#include "httpd.h"
#include "http_config.h"
#include "http_log.h"
#include "http_conf_globals.h"
#include <unistd.h>
#include <sys/stat.h>
#include <pwd.h>
#include <sys/param.h>
#include <sys/mount.h>
#include <pthread.h>
#include <mach/mach_init.h>
#include <mach/mach_port.h>
#define getModConfig() (module_cfg_rec *)ap_ctx_get( ap_global_ctx, "rendezvous_apple_module" )
#define TITLE_MAX 127
#define TXT_MAX 511
#define REGNAME_MAX 255
#define PORT_MAX 65535
#define MAX_NAME_FORMAT 64
#define ALL_USERS "all-users"
#define CUSTOMIZED_USERS "customized-users"
#define DEFAULT_NAME_FORMAT "%l"
typedef struct replyRec {
char name[REGNAME_MAX+1];
char txt[TXT_MAX+1];
int port;
dns_service_discovery_ref serviceRef;
server_rec* serverData;
CFRunLoopSourceRef runLoopSource;
Boolean shouldFreeInfo;
CFMachPortRef cfMachPort;
mach_port_t machPort;
} replyRec;
typedef struct module_cfg_rec {
pool *pPool;
int initCount; array_header *replyRecPtrArray;
pthread_mutex_t threadMutex;
pthread_cond_t cond;
replyRec* initialRegReplyData;
CFRunLoopRef replyRunLoop;
int globalErr;
} module_cfg_rec;
typedef struct server_cfg_rec { int cmode;
#define CONFIG_MODE_SERVER 1
#define CONFIG_MODE_DIRECTORY 2
#define CONFIG_MODE_COMBO 3
int local;
int congenital;
char *loc;
} server_cfg_rec;
module MODULE_VAR_EXPORT rendezvous_apple_module;
static int allUsersDone = 0;
static void* templateIndexMM = 0;
static int templateIndexFD = 0;
static struct stat templateIndexFinfo;
static void awaitRendezvousCallbacks( server_rec* serverData );
static void unregisterRefs();
static int createCallbackThread( server_rec *serverData );
static void registerService( char* inName, int inPort, char* inTxt, server_rec* serverData );
static void handleMachMessage( CFMachPortRef port, void *msg, CFIndex size, void *info );
static void regReply( DNSServiceRegistrationReplyErrorType inErrorCode, void *replyDataVoid );
static tDirStatus getDefaultLocalNode ( tDirReference inDirRef, tDirNodeReference *outLocalNodeRef );
static tDirStatus processUsers( tDirReference dirRef, tDirNodeReference defaultLocalNodeRef,
char* whichUsers, char* regNameFormat, int port, cmd_parms *cmd );
static int getUserSitePath( char *inUserName, char *outSitePath, cmd_parms* cmd );
static char* extractHTMLTitle( char* fileName, cmd_parms* cmd );
static Boolean userHasValidCustomizedSite( char* inUserName, cmd_parms* cmd );
static void registerUser( char* inUserName, char* inRegNameFormat, int inPort, cmd_parms *cmd );
static void getTitle( char* inSiteFolder, char* outTitle, cmd_parms* cmd );
static void registerUserTitle( char* inUserName, int inPort, cmd_parms *cmd );
static void registerUsers( char* whichUsers, char* regNameFormat, int port, cmd_parms *cmd );
static const char *processRegDefaultSite( cmd_parms *cmd, void *dummy, const char *arg );
static const char *processRegUserSite( cmd_parms *cmd, void *dummy, char *inName, char *inPort, char *inHost );
static const char *processRegResource( cmd_parms *cmd, void *dummy, char *inName, char *inPath, char* inPort );
static void registerComputerName( server_rec *serverData, int inPort );
static void rendezvousChildInit( server_rec *serverData, pool *p );
static void rendezvousModuleInit( server_rec *serverData, pool *p );
static void *rendezvousModuleCreateServerConfig( pool *p, char *dirspec );
static void awaitRendezvousCallbacks( server_rec* serverData ) {
int status = 0;
module_cfg_rec *module_cfg = getModConfig();
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
pthread_mutex_lock(&module_cfg->threadMutex);
module_cfg->replyRunLoop = runLoop;
pthread_mutex_unlock(&module_cfg->threadMutex);
pthread_cond_signal(&module_cfg->cond);
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, serverData,
"%s In awaitRendezvousCallbacks.",
MSG_PREFIX );
CFRunLoopAddSource( CFRunLoopGetCurrent(), module_cfg->initialRegReplyData->runLoopSource,
kCFRunLoopCommonModes );
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, serverData,
"%s Now awaiting callbacks for initial registration name '%s'.",
MSG_PREFIX, module_cfg->initialRegReplyData->name );
CFRunLoopRun();
module_cfg->replyRunLoop = NULL;
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, serverData,
"%s No more registrations.",
MSG_PREFIX );
return;
}
static void unregisterRefs() {
int i;
module_cfg_rec *module_cfg = getModConfig();
replyRec** replyDataPtrs = NULL;
replyDataPtrs = (replyRec**)module_cfg->replyRecPtrArray->elts;
for (i = 0; i < module_cfg->replyRecPtrArray->nelts; i++) {
if (!replyDataPtrs[i])
continue;
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, replyDataPtrs[i]->serverData,
"%s DeRegistering '%s' cfMachPort %d from pid %d",
MSG_PREFIX, replyDataPtrs[i]->name, (int) replyDataPtrs[i]->cfMachPort, getpid() );
if (replyDataPtrs[i]->serviceRef) {
DNSServiceDiscoveryDeallocate( replyDataPtrs[i]->serviceRef );
replyDataPtrs[i]->serviceRef = 0;
}
}
for (i = 0; i < module_cfg->replyRecPtrArray->nelts; i++) {
if (!replyDataPtrs[i])
continue;
if (replyDataPtrs[i]->runLoopSource) {
CFRunLoopSourceInvalidate( replyDataPtrs[i]->runLoopSource );
CFRunLoopRemoveSource( module_cfg->replyRunLoop, replyDataPtrs[i]->runLoopSource, kCFRunLoopDefaultMode );
CFRelease( replyDataPtrs[i]->runLoopSource );
}
if (replyDataPtrs[i]->cfMachPort) {
CFMachPortInvalidate( replyDataPtrs[i]->cfMachPort );
CFRelease( replyDataPtrs[i]->cfMachPort );
}
if (replyDataPtrs[i]->machPort && replyDataPtrs[i]->shouldFreeInfo)
mach_port_deallocate( mach_task_self(), replyDataPtrs[i]->machPort );
free( replyDataPtrs[i] );
}
module_cfg->replyRecPtrArray->nelts = 0;
if (templateIndexMM > 0) {
close( templateIndexFD );
munmap( templateIndexMM, templateIndexFinfo.st_size );
templateIndexMM = 0;
}
}
static int createCallbackThread( server_rec *serverData ) {
pthread_attr_t theThreadAttrs;
pthread_t thread;
int status;
module_cfg_rec *module_cfg = getModConfig();
status = pthread_attr_init( &theThreadAttrs );
if (status) {
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, serverData,
"%s Error allocating thread attribute.",
MSG_PREFIX );
return status;
}
status = pthread_attr_setdetachstate( &theThreadAttrs, PTHREAD_CREATE_DETACHED );
if (status) {
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, serverData,
"%s Error setting thread attribute; cannot confirm registration.",
MSG_PREFIX );
return 1;
}
status = pthread_create( &thread, &theThreadAttrs, (void*) awaitRendezvousCallbacks,
(void*) serverData );
if (status) {
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, serverData,
"%s Cannot create registration callback thread; cannot confirm any registrations.",
MSG_PREFIX );
return 1;
}
status = pthread_attr_destroy( &theThreadAttrs );
if (status) {
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, serverData,
"%s Cannot destroy registration callback thread attributes.",
MSG_PREFIX );
return 0;
}
return 0;
}
static void registerService( char* inName, int inPort, char* inTxt, server_rec* serverData ) {
int status;
module_cfg_rec *module_cfg = getModConfig();
replyRec* replyData = (replyRec*) malloc( sizeof(replyRec) );
if (!replyData) {
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, serverData,
"%s Error allocating memory, cannot register '%s'.",
MSG_PREFIX, inName );
return;
}
if (inName)
strncpy( replyData->name, inName, sizeof(replyData->name) );
if (inTxt)
strncpy( replyData->txt, inTxt, sizeof(replyData->txt) );
replyData->port = inPort;
replyData->serverData = serverData;
replyData->serviceRef = DNSServiceRegistrationCreate( inName, "_http._tcp", "",
htons( inPort ), inTxt, regReply, (void*)replyData );
if (!replyData->serviceRef) {
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, serverData,
"%s Error trying to register '%s' from pid=%d.",
MSG_PREFIX, inName, getpid() );
free( replyData );
return;
}
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, serverData,
"%s Registered name='%s' txt='%s' port=%d from pid=%d; awaiting confirmation.",
MSG_PREFIX, inName, inTxt, inPort, getpid() );
replyData->machPort = DNSServiceDiscoveryMachPort( replyData->serviceRef );
if (!replyData->machPort) {
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, serverData,
"%s Error retrieving mach port; will not receive callbacks for name='%s'.",
MSG_PREFIX, inName );
return;
}
CFMachPortContext context = { 0, 0, NULL, NULL, NULL };
replyData->cfMachPort = CFMachPortCreateWithPort( kCFAllocatorDefault, replyData->machPort,
handleMachMessage, &context, &(replyData->shouldFreeInfo) );
if (!replyData->cfMachPort) {
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, serverData,
"%s Could not create cfMachPort; will not receive callbacks for name='%s'.",
MSG_PREFIX, inName );
return;
}
replyData->runLoopSource = CFMachPortCreateRunLoopSource( NULL, replyData->cfMachPort, 0 );
if (!replyData->runLoopSource) {
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, serverData,
"%s Could not create runloop source from mach port; will not receive callbacks for name='%s'.",
MSG_PREFIX, inName );
return;
}
module_cfg->initialRegReplyData = replyData;
if (!module_cfg->replyRunLoop) {
struct timeval now;
struct timespec waitTime = { 0, 0 };
if (createCallbackThread( serverData )) {
module_cfg->globalErr = 1;
return;
}
gettimeofday(&now, NULL);
waitTime.tv_sec = now.tv_sec + 10;
pthread_mutex_lock(&module_cfg->threadMutex);
while (!module_cfg->replyRunLoop) {
int ret = pthread_cond_timedwait(&module_cfg->cond, &module_cfg->threadMutex, &waitTime);
if (ETIMEDOUT == ret) {
pthread_mutex_unlock(&module_cfg->threadMutex);
module_cfg->globalErr = 1;
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, serverData,
"%s timeout waiting for thread to start.",
MSG_PREFIX );
return;
}
}
pthread_mutex_unlock(&module_cfg->threadMutex);
}
else {
CFRunLoopAddSource( module_cfg->replyRunLoop, replyData->runLoopSource, kCFRunLoopCommonModes );
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, serverData,
"%s Now awaiting callbacks for name='%s'.",
MSG_PREFIX, inName );
}
}
static void handleMachMessage( CFMachPortRef port, void *msg, CFIndex size, void *info ) {
DNSServiceDiscovery_handleReply( msg );
}
static void regReply( DNSServiceRegistrationReplyErrorType inErrorCode, void *replyDataVoid ) {
char newName[REGNAME_MAX+1];
void* unusedValue = NULL;
replyRec* replyData = (replyRec*)replyDataVoid;
module_cfg_rec *module_cfg = getModConfig();
int status = 0;
Boolean threadSafe = false;
replyRec** saveReplyRec;
int i;
replyRec** replyDataPtrs = NULL;
replyDataPtrs = (replyRec**)module_cfg->replyRecPtrArray->elts;
Boolean found;
status = pthread_mutex_lock( &(module_cfg->threadMutex) );
threadSafe = (status == 0);
switch (inErrorCode) {
case kDNSServiceDiscoveryNoError:
if (threadSafe) {
found = FALSE;
for (i = 0; i < module_cfg->replyRecPtrArray->nelts; i++) {
if (!strcmp( replyDataPtrs[i]->name, replyData->name )) {
found = TRUE;
break;
}
}
if (found)
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, replyData->serverData,
"%s Re-registration confirmed for name='%s', port=%d.",
MSG_PREFIX, replyData->name, replyData->port );
else {
saveReplyRec = (replyRec **)ap_push_array( module_cfg->replyRecPtrArray );
*saveReplyRec = replyData;
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, replyData->serverData,
"%s Registration confirmed for name='%s', port=%d.",
MSG_PREFIX, replyData->name, replyData->port );
}
}
break;
case kDNSServiceDiscoveryNameConflict:
if (threadSafe) {
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, replyData->serverData,
"%s Name %s in use; trying again with a new name", MSG_PREFIX, replyData->name );
if (replyData->serverData->server_hostname)
snprintf( newName, sizeof(newName), "%s - %s", replyData->name, replyData->serverData->server_hostname );
else
snprintf( newName, sizeof(newName), "%s - %ld", replyData->name, random() % 100 );
registerService( newName, replyData->port, replyData->txt, replyData->serverData );
}
break;
default:
if (threadSafe) {
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, replyData->serverData,
"%s Registration callback received unexpected error %d for name '%s'.",
MSG_PREFIX, inErrorCode, replyData->name );
}
}
if (threadSafe) {
status = pthread_mutex_unlock( &(module_cfg->threadMutex) );
if (status)
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, replyData->serverData,
"%s Registration callback cannot release mutex lock for name '%s'.",
MSG_PREFIX, replyData->name );
}
}
static tDirStatus getDefaultLocalNode( tDirReference inDirRef, tDirNodeReference *outLocalNodeRef )
{
tDirStatus dsStatus = eMemoryAllocError;
unsigned long nodeCount = 0;
tDataBuffer *pTDataBuff = NULL;
tDataList *pDataList = NULL;
pTDataBuff = dsDataBufferAllocate( inDirRef, 8*1024 );
if ( pTDataBuff != NULL ) {
dsStatus = dsFindDirNodes( inDirRef, pTDataBuff, NULL, eDSLocalNodeNames, &nodeCount, NULL );
if ( dsStatus == eDSNoErr ) {
dsStatus = dsGetDirNodeName( inDirRef, pTDataBuff, 1, &pDataList );
if ( dsStatus == eDSNoErr )
dsStatus = dsOpenDirNode( inDirRef, pDataList, outLocalNodeRef );
if ( pDataList != NULL ) {
(void)dsDataListDeallocate( inDirRef, pDataList );
free( pDataList );
pDataList = NULL;
}
}
(void)dsDataBufferDeAllocate( inDirRef, pTDataBuff );
pTDataBuff = NULL;
}
return dsStatus;
}
static tDirStatus processUsers( tDirReference inDirRef, tDirNodeReference inNodeRef, char* whichUsers, char* regNameFormat, int inPort, cmd_parms *cmd ) {
tDataBufferPtr nameDataBufferPtr;
tDataList recTypes;
tDataList recNames;
tDataList attributeList;
tRecordEntryPtr recordEntryPtr;
tAttributeListRef attrListRef = 0;
tDirStatus dsStatus;
unsigned long recordCount = 0;
unsigned long i;
tContextData contextDataRecList = NULL;
tAttributeValueListRef attrValueListRef;
tAttributeEntryPtr attrEntryPtr;
tAttributeValueEntryPtr attrValueEntryPtr;
char* recordName;
if( !(nameDataBufferPtr = dsDataBufferAllocate( inDirRef, 8*1024 )) ) {
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, cmd->server,
"%s Failure of dsDataBufferAllocate",
MSG_PREFIX );
return -1;
}
dsStatus = dsBuildListFromStringsAlloc( inDirRef, &recTypes, kDSStdRecordTypeUsers, NULL );
if (dsStatus != eDSNoErr) {
dsDataBufferDeAllocate( inDirRef, nameDataBufferPtr );
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, cmd->server,
"%s Failure of dsDataBufferDeAllocate: %d",
MSG_PREFIX, dsStatus );
return dsStatus;
}
dsStatus = dsBuildListFromStringsAlloc( inDirRef, &recNames, kDSRecordsAll, NULL );
if (dsStatus != eDSNoErr) {
dsDataBufferDeAllocate( inDirRef, nameDataBufferPtr );
dsDataListDeallocate( inDirRef, &recTypes );
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, cmd->server,
"%s Failure of dsBuildListFromStringsAlloc: %d",
MSG_PREFIX, dsStatus );
return dsStatus;
}
dsStatus = dsBuildListFromStringsAlloc( inDirRef, &attributeList, kDSNAttrRecordName,
kDS1AttrUniqueID, NULL );
if (dsStatus != eDSNoErr) {
dsDataBufferDeAllocate( inDirRef, nameDataBufferPtr );
dsDataListDeallocate( inDirRef, &recTypes );
dsDataListDeallocate( inDirRef, &recNames );
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, cmd->server,
"%s Failure of dsBuildListFromStringsAlloc: %d",
MSG_PREFIX, dsStatus );
return dsStatus;
}
do {
dsStatus = dsGetRecordList( inNodeRef, nameDataBufferPtr, &recNames, eDSiExact, &recTypes,
&attributeList, 0, &recordCount, &contextDataRecList );
if (dsStatus != eDSNoErr) {
dsDataListDeallocate( inDirRef, &attributeList );
dsDataBufferDeAllocate( inDirRef, nameDataBufferPtr );
dsDataListDeallocate( inDirRef, &recTypes );
dsDataListDeallocate( inDirRef, &recNames );
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, cmd->server,
"%s Failure of dsGetRecordList: %d",
MSG_PREFIX, dsStatus );
return dsStatus;
}
for (i = 1; i <= recordCount; i++) {
dsStatus = dsGetRecordEntry( inNodeRef, nameDataBufferPtr, i, &attrListRef,
&recordEntryPtr );
if (dsStatus != eDSNoErr) {
dsDataListDeallocate( inDirRef, &attributeList );
dsDataBufferDeAllocate( inDirRef, nameDataBufferPtr );
dsDataListDeallocate( inDirRef, &recTypes );
dsDataListDeallocate( inDirRef, &recNames );
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, cmd->server,
"%s Failure of dsGetRecordEntry: %d, i=%lu",
MSG_PREFIX, dsStatus, i );
return dsStatus;
}
dsStatus = dsGetAttributeEntry( inNodeRef, nameDataBufferPtr, attrListRef, 1, &attrValueListRef,
&attrEntryPtr );
if (dsStatus != eDSNoErr) {
dsDataListDeallocate( inDirRef, &attributeList );
dsDataBufferDeAllocate( inDirRef, nameDataBufferPtr );
dsDataListDeallocate( inDirRef, &recTypes );
dsDataListDeallocate( inDirRef, &recNames );
dsCloseAttributeList( attrListRef );
dsDeallocRecordEntry( inDirRef, recordEntryPtr );
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, cmd->server,
"%s Failure of dsGetAttributeEntry: %d, i=%lu",
MSG_PREFIX, dsStatus, i );
return dsStatus;
}
dsStatus = dsGetAttributeValue( inNodeRef, nameDataBufferPtr, 1, attrValueListRef,
&attrValueEntryPtr);
if (dsStatus != eDSNoErr) {
dsDataListDeallocate( inDirRef, &attributeList );
dsDataBufferDeAllocate( inDirRef, nameDataBufferPtr );
dsDataListDeallocate( inDirRef, &recTypes );
dsDataListDeallocate( inDirRef, &recNames );
dsDeallocAttributeEntry( inDirRef, attrEntryPtr );
dsCloseAttributeValueList( attrValueListRef );
dsCloseAttributeList( attrListRef );
dsDeallocRecordEntry( inDirRef, recordEntryPtr );
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, cmd->server,
"%s Failure of dsGetAttributeValue: %d, i=%lu",
MSG_PREFIX, dsStatus, i );
return dsStatus;
}
recordName = attrValueEntryPtr->fAttributeValueData.fBufferData;
if (!strcmp( whichUsers, ALL_USERS ))
registerUser( recordName, regNameFormat, inPort, cmd );
else if (!strcmp( whichUsers, CUSTOMIZED_USERS )) {
if (userHasValidCustomizedSite( recordName, cmd ))
registerUser( recordName, regNameFormat, inPort, cmd );
else
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, cmd->server,
"%s Skipping non-customized user %s",
MSG_PREFIX, recordName );
}
else
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, cmd->server,
"%s Unexpected 'RegisterUserSite %s', not registering.",
MSG_PREFIX, whichUsers );
dsDeallocAttributeValueEntry( inDirRef, attrValueEntryPtr );
dsDeallocAttributeEntry( inDirRef, attrEntryPtr );
dsCloseAttributeValueList( attrValueListRef );
dsCloseAttributeList( attrListRef );
dsDeallocRecordEntry( inDirRef, recordEntryPtr );
}
} while (contextDataRecList != NULL);
dsDataBufferDeAllocate( inDirRef, nameDataBufferPtr );
dsDataListDeallocate( inDirRef, &attributeList );
dsDataListDeallocate( inDirRef, &recTypes );
dsDataListDeallocate( inDirRef, &recNames );
return eDSNoErr;
}
static int getUserSitePath( char *inUserName, char *outSitePath, cmd_parms* cmd ) {
typedef struct userdir_config {
int globally_disabled;
char *userdir;
table *enabled_users;
table *disabled_users;
} userdir_config;
userdir_config *s_cfg;
module* userdir_mod = ap_find_linked_module( "mod_userdir.c" );
if (!userdir_mod) {
ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, cmd->server,
"%s Mod_userdir not loaded", MSG_PREFIX);
return 1;
}
s_cfg = (userdir_config *) ap_get_module_config( cmd->server->module_config, userdir_mod );
if (!s_cfg) {
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, cmd->server,
"%s Mod_userdir config not present", MSG_PREFIX );
return 1;
}
if (!s_cfg->userdir) {
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, cmd->server,
"%s Null userdir", MSG_PREFIX );
return 1;
}
const char *userdirs = s_cfg->userdir;
struct stat statbuf;
if (ap_table_get( s_cfg->disabled_users, inUserName ) != NULL) {
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, cmd->server,
"%s User '%s' is in disabled users list; not registering", MSG_PREFIX, inUserName );
return 1;
}
if (s_cfg->globally_disabled
&& (ap_table_get( s_cfg->enabled_users, inUserName ) == NULL)) {
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, cmd->server,
"%s Userdir globally disabled, and user '%s' is not an exception; not registering",
MSG_PREFIX, inUserName );
return 1;
}
while (*userdirs) {
const char *userdir = ap_getword_conf( cmd->pool, &userdirs );
char *filename = NULL;
int is_absolute = ap_os_is_path_absolute( userdir );
if (strchr( userdir, '*' )) {
char *x = ap_getword( cmd->pool, &userdir, '*' );
if (is_absolute) {
filename = ap_pstrcat( cmd->pool, x, inUserName, userdir, NULL );
}
else if (strchr( x, ':' )) {
break;
}
else {
break;
}
}
else if (is_absolute) {
if (userdir[strlen( userdir ) - 1] == '/')
filename = ap_pstrcat( cmd->pool, userdir, inUserName, NULL );
else
filename = ap_pstrcat( cmd->pool, userdir, "/", inUserName, NULL );
}
else if (strchr( userdir, ':' )) {
break;
}
else {
struct passwd *pw;
if ((pw = getpwnam( inUserName ))) {
filename = ap_pstrcat( cmd->pool, pw->pw_dir, "/", userdir, NULL );
}
}
if (filename && (stat( filename, &statbuf ) != -1)) {
strncpy( outSitePath, filename, PATH_MAX );
return 0;
}
}
return 1;
}
static char *extractHTMLTitle( char* fileName, cmd_parms* cmd )
{
char titlebuf[MAX_STRING_LEN], *find = "<TITLE>";
FILE *thefile = NULL;
int x, y, n, p;
if (!(thefile = ap_pfopen(cmd->pool, fileName, "r"))) {
return NULL;
}
n = fread(titlebuf, sizeof(char), MAX_STRING_LEN - 1, thefile);
if (n <= 0) {
ap_pfclose(cmd->pool, thefile);
return NULL;
}
titlebuf[n] = '\0';
for (x = 0, p = 0; titlebuf[x]; x++) {
if (ap_toupper(titlebuf[x]) == find[p]) {
if (!find[++p]) {
if ((p = ap_ind(&titlebuf[++x], '<')) != -1) {
titlebuf[x + p] = '\0';
}
for (y = x; titlebuf[y]; y++) {
if ((titlebuf[y] == CR) || (titlebuf[y] == LF)) {
if (y == x) {
x++;
}
else {
titlebuf[y] = ' ';
}
}
}
ap_pfclose(cmd->pool, thefile);
return ap_pstrdup(cmd->pool, &titlebuf[x]);
}
}
else {
p = 0;
}
}
ap_pfclose(cmd->pool, thefile);
}
static void getTitle( char* inSiteFolder, char* outTitle, cmd_parms* cmd ) {
typedef struct dir_config_struct {
array_header *index_names;
} dir_config_rec;
int i;
dir_config_rec *dir_cfg;
char *dummy_ptr[1];
char **dirIndexNames;
int dirIndexNameCount;
module* dir_mod = ap_find_linked_module( "mod_dir.c" );
if (!dir_mod) {
ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, cmd->server,
"%s Mod_dir not loaded", MSG_PREFIX);
*outTitle = NULL;
return;
}
dir_cfg = (dir_config_rec *) ap_get_module_config( cmd->server->lookup_defaults, dir_mod );
if (!dir_cfg) {
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, cmd->server,
"%s Mod_dir config not present", MSG_PREFIX );
*outTitle = NULL;
return;
}
if (dir_cfg->index_names) {
dirIndexNames = (char **)dir_cfg->index_names->elts;
dirIndexNameCount = dir_cfg->index_names->nelts;
}
else {
dummy_ptr[0] = DEFAULT_INDEX;
dirIndexNames = dummy_ptr;
dirIndexNameCount = 1;
}
for (; dirIndexNameCount; ++dirIndexNames, --dirIndexNameCount) {
char *dirIndexName = *dirIndexNames;
char fullSitePath[PATH_MAX+1];
strncpy( fullSitePath, inSiteFolder, sizeof(fullSitePath) );
strcat( fullSitePath, "/" );
strncat( fullSitePath, dirIndexName, sizeof(fullSitePath) - strlen( fullSitePath ) );
if ((strlen( dirIndexName ) > 5 && !strcmp( &(dirIndexName[strlen( dirIndexName ) - 5]), ".html" ))
|| (strlen( dirIndexName ) > 4 && !strcmp( &(dirIndexName[strlen( dirIndexName ) - 4]), ".htm" ))) {
char* titleStr = extractHTMLTitle( fullSitePath, cmd );
if (!titleStr || !strcmp( titleStr, "" )) {
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, cmd->server,
"%s Index file %s has no title.",
MSG_PREFIX, fullSitePath );
*outTitle = NULL;
return;
}
strncpy( outTitle, titleStr, TITLE_MAX );
return;
}
}
*outTitle = NULL;
}
static Boolean userHasValidCustomizedSite( char* inUserName, cmd_parms* cmd ) {
char site_path[PATH_MAX+1];
int filesDiffer;
int userIndexFD;
struct stat userIndexFinfo;
void* userIndexMM;
#define TEMPLATE_PATH "/System/Library/User Template/English.lproj/Sites/index.html"
if (templateIndexMM == 0 || templateIndexMM == (void*) -1 ) {
if (stat( TEMPLATE_PATH, &templateIndexFinfo ) == -1) {
ap_log_error( APLOG_MARK, APLOG_WARNING, cmd->server,
"%s Skipping user '%s' - cannot stat template index file '%s'.",
MSG_PREFIX, inUserName, TEMPLATE_PATH );
return FALSE;
}
templateIndexFD = open( TEMPLATE_PATH, O_RDONLY, 0 );
templateIndexMM = mmap( NULL, templateIndexFinfo.st_size, PROT_READ, MAP_SHARED, templateIndexFD, 0 );
if ( templateIndexMM == (void*) -1) {
ap_log_error( APLOG_MARK, APLOG_WARNING, cmd->server,
"%s Skipping user '%s' - cannot read template index file '%s'.",
MSG_PREFIX, inUserName, TEMPLATE_PATH );
close( userIndexFD );
return FALSE;
}
}
if (getUserSitePath( inUserName, &site_path, cmd )) {
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, cmd->server,
"%s Skipping user '%s' - unable to confirm userdir site.",
MSG_PREFIX, inUserName );
return FALSE;
}
strcat( site_path, "/index.html" );
if (stat( site_path, &userIndexFinfo ) == -1) {
ap_log_error( APLOG_MARK, - APLOG_NOERRNO|APLOG_WARNING, cmd->server,
"%s Skipping user '%s' - cannot read index file '%s'.",
MSG_PREFIX, inUserName, site_path );
return FALSE;
}
if ((userIndexFinfo.st_mode & S_IFMT) != S_IFREG) {
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, cmd->server,
"%s Skipping user '%s' - index file isn't a regular file.",
MSG_PREFIX, inUserName, site_path );
return FALSE;
}
if (userIndexFinfo.st_size != templateIndexFinfo.st_size) {
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, cmd->server,
"%s Concluding user '%s' has customized site - sizes differ %qd vs %qd.",
MSG_PREFIX, inUserName, userIndexFinfo.st_size, templateIndexFinfo.st_size );
return TRUE;
}
userIndexFD = open( site_path, O_RDONLY, 0 );
if (userIndexFD == -1) {
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, cmd->server,
"%s Skipping user '%s' - cannot open index file '%s'.",
MSG_PREFIX, inUserName, site_path );
return FALSE;
}
userIndexMM = mmap( NULL, userIndexFinfo.st_size, PROT_READ, MAP_SHARED, userIndexFD, 0);
if ( userIndexMM == (void*) -1) {
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, cmd->server,
"%s Skipping user '%s' - cannot read index file '%s'.",
MSG_PREFIX, inUserName, site_path );
close( userIndexFD );
return FALSE;
}
filesDiffer = memcmp( templateIndexMM, userIndexMM, userIndexFinfo.st_size );
munmap( userIndexMM, userIndexFinfo.st_size );
close( userIndexFD );
if (filesDiffer != 0) {
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, cmd->server,
"%s Concluding user '%s' has customized site - contents differ.",
MSG_PREFIX, inUserName );
return TRUE;
}
return FALSE;
}
static void registerUser( char* inUserName, char* inRegNameFormat, int inPort, cmd_parms* cmd ) {
char txt[TXT_MAX+1];
char site_path[PATH_MAX+1];
char title[TITLE_MAX+1];
char regName[REGNAME_MAX+1];
struct passwd* pw = getpwnam( inUserName );
if (!pw) {
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, cmd->server,
"%s Skipping user '%s' - no pw entry.",
MSG_PREFIX, inUserName );
return;
}
if ((int)pw->pw_uid < 100) {
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, cmd->server,
"%s Skipping user '%s' - uid '%d' indicates system user.",
MSG_PREFIX, pw->pw_name, pw->pw_uid );
return;
}
if (getUserSitePath( inUserName, &site_path, cmd )) {
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, cmd->server,
"%s Skipping user '%s' - unable to confirm userdir site.",
MSG_PREFIX, pw->pw_name );
return;
}
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, cmd->server,
"%s Confirmed user '%s' has existing userdir site directory '%s'.",
MSG_PREFIX, pw->pw_name, site_path );
snprintf( txt, sizeof(txt), "path=/~%s/", inUserName );
if (!strcasecmp( inRegNameFormat, "longname" ))
strncpy( regName, pw->pw_gecos, sizeof(regName) );
else if (!strcasecmp( inRegNameFormat, "title" )) {
getTitle( site_path, &title, cmd );
if (title && strcmp( title, "" ))
strncpy( regName, title, sizeof(regName) );
else
strncpy( regName, pw->pw_gecos, sizeof(regName) );
}
else if (!strcasecmp( inRegNameFormat, "longname-title" )) {
getTitle( site_path, &title, cmd );
if (title && strcmp( title, "" ))
snprintf( regName, sizeof(regName), "%s - %s", pw->pw_gecos, title );
else
strncpy( regName, pw->pw_gecos, sizeof(regName) );
}
else {
int len = strlen(inRegNameFormat);
int i;
int j = 0;
char uidStr[8];
CFStringRef hostRef;
CFStringEncoding enc;
char hostStr[65];
bzero( regName, sizeof(regName) );
for (i = 0; i < len; i++) {
if (inRegNameFormat[i] == '%') {
switch (inRegNameFormat[i + 1]) {
case 't': getTitle( site_path, &title, cmd );
if (title && strcmp( title, "" )) {
strncat( regName, title, sizeof(regName) );
j = j + strlen( title );
}
i++;
break;
case 'l': strncat( regName, pw->pw_gecos, sizeof(regName) );
j = j + strlen( pw->pw_gecos );
i++;
break;
case 'n': strncat( regName, pw->pw_name, sizeof(regName) );
j = j + strlen( pw->pw_name );
i++;
break;
case 'u': sprintf( uidStr, "%d", pw->pw_uid );
strncat( regName, uidStr, sizeof(regName) );
j = j + strlen( uidStr );
i++;
break;
case 'c': hostRef = SCDynamicStoreCopyComputerName( NULL, NULL );
CFStringGetCString( hostRef, hostStr, sizeof(hostStr), enc );
strncat( regName, hostStr, sizeof(regName) );
j = j + strlen( hostStr );
i++;
break;
default:
regName[j++] = inRegNameFormat[i];
}
}
else
regName[j++] = inRegNameFormat[i];
}
}
registerService( regName, inPort, txt, cmd->server );
return;
}
static void registerUsers( char* whichUsers, char* regNameFormat, int port, cmd_parms *cmd ) {
tDirStatus dsStatus;
tDirReference dirRef;
tDirNodeReference defaultLocalNodeRef;
dsStatus = dsOpenDirService( &dirRef );
if (dsStatus != eDSNoErr) {
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, cmd->server,
"%s Unable to open Directory Services (error = %d).", MSG_PREFIX, dsStatus );
return;
}
dsStatus = getDefaultLocalNode( dirRef, &defaultLocalNodeRef );
if (dsStatus != eDSNoErr) {
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, cmd->server,
"%s Unable to open Directory Services local node (error = %d).", MSG_PREFIX, dsStatus );
(void)dsCloseDirService( dirRef );
return;
}
dsStatus = processUsers( dirRef, defaultLocalNodeRef, whichUsers, regNameFormat, port, cmd );
if (dsStatus != eDSNoErr) {
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, cmd->server,
"%s Unable to process all users (error = %d).", MSG_PREFIX, dsStatus );
(void)dsCloseDirService( dirRef );
return;
}
(void)dsCloseDirNode( defaultLocalNodeRef );
(void)dsCloseDirService( dirRef );
}
static const char *processRegDefaultSite( cmd_parms *cmd, void *dummy, const char *arg ) {
module_cfg_rec *module_cfg = getModConfig();
if (module_cfg->initCount < 1) {
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, cmd->server,
"%s Skipping bootstrap config for default site; count=%d",
MSG_PREFIX, module_cfg->initCount );
return NULL;
}
if (module_cfg->globalErr) {
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, cmd->server,
"%s Global error, unable to process RegisterDefaultSite directive",
MSG_PREFIX );
return NULL;
}
int port = DEFAULT_HTTP_PORT;
int err = 0;
const char *errString = ap_check_cmd_context( cmd, GLOBAL_ONLY );
if (errString != NULL) {
return errString;
}
char* portArg = ap_getword_conf( cmd->pool, &arg );
if (portArg && strcmp( portArg, "" )) {
if (strlen( portArg ) > 5)
return ap_pstrcat( cmd->pool, MSG_PREFIX, "Port argument too long", NULL );
if (!strcasecmp( portArg, "main" )) port = cmd->server->port;
else {
err = sscanf( portArg, "%d", &port );
if (!err)
return ap_pstrcat( cmd->pool, MSG_PREFIX, "Port argument not 'main' or numeric", NULL );
if (port < 0 || port > PORT_MAX)
return ap_pstrcat( cmd->pool, MSG_PREFIX, "Port argument out of range", NULL );
}
}
if (!ap_configtestonly)
registerComputerName( cmd->server, port );
return NULL;
}
static const char *processRegUserSite( cmd_parms *cmd, void *dummy, char *inName, char *inRegNameFormat, char *inPort ) {
int port = DEFAULT_HTTP_PORT;
int err = 0;
module_cfg_rec *module_cfg = getModConfig();
if (module_cfg->initCount < 1) {
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, cmd->server,
"%s Skipping bootstrap config for user sites; count=%d",
MSG_PREFIX, module_cfg->initCount );
return NULL;
}
if (module_cfg->globalErr) {
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, cmd->server,
"%s Global error, unable to process RegisterUserSite directive",
MSG_PREFIX );
return NULL;
}
const char *errString = ap_check_cmd_context( cmd, GLOBAL_ONLY );
if (errString != NULL) {
return errString;
}
if (!inName || !strcmp( inName, "" )) {
return ap_pstrcat( cmd->pool, MSG_PREFIX, "Name argument missing", NULL );
}
if (strlen( inName ) > MAXLOGNAME) {
return ap_pstrcat( cmd->pool, MSG_PREFIX, "Name argument too long", NULL );
}
if (inRegNameFormat && strlen( inRegNameFormat ) > MAX_NAME_FORMAT) {
return ap_pstrcat( cmd->pool, MSG_PREFIX, "Name format argument too long", NULL );
}
if (inPort && strcmp( inPort, "" )) {
if (strlen( inPort ) > 5)
return ap_pstrcat( cmd->pool, MSG_PREFIX, "Port argument too long", NULL );
if (!strcasecmp( inPort, "main" )) port = cmd->server->port;
else {
err = sscanf( inPort, "%d", &port );
if (!err)
return ap_pstrcat( cmd->pool, MSG_PREFIX, "Port argument not 'main' or numeric", NULL );
if (port < 0 || port > PORT_MAX)
return ap_pstrcat( cmd->pool, MSG_PREFIX, "Port argument out of range", NULL );
}
}
if (!strcasecmp( inName, ALL_USERS ) || !strcasecmp( inName, CUSTOMIZED_USERS ) ) {
if (!ap_configtestonly) {
if (allUsersDone) {
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, cmd->server,
"%s Skipping redundant request to register multiple users", MSG_PREFIX );
}
else {
if (inRegNameFormat)
registerUsers( inName, inRegNameFormat, port, cmd );
else
registerUsers( inName, DEFAULT_NAME_FORMAT, port, cmd );
allUsersDone = 1;
}
}
}
else if (!ap_configtestonly)
registerUser( inName, inRegNameFormat, port, cmd );
return NULL;
}
static const char *processRegResource( cmd_parms *cmd, void *dummy, char *inName, char *inPath, char* inPort ) {
char txt[TXT_MAX+1];
int port = DEFAULT_HTTP_PORT;
int err = 0;
module_cfg_rec *module_cfg = getModConfig();
if (module_cfg->initCount < 1) {
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, cmd->server,
"%s Skipping bootstrap config for resource; count=%d",
MSG_PREFIX, module_cfg->initCount );
return NULL;
}
if (module_cfg->globalErr) {
ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, cmd->server,
"%s Global error, unable to process RegisterResource directive",
MSG_PREFIX );
return NULL;
}
const char *errString = ap_check_cmd_context( cmd, GLOBAL_ONLY );
if (errString != NULL) {
return errString;
}
if (inName && strcmp(inName, "" ))
if (strlen( inName ) > REGNAME_MAX)
return ap_pstrcat( cmd->pool, MSG_PREFIX, "Name argument too long", NULL );
if (inPath && strcmp(inPath, "" )) {
if (strlen( inPath ) > PATH_MAX)
return ap_pstrcat( cmd->pool, MSG_PREFIX, "Path argument too long to be a path", NULL );
if (strlen( inPath ) > TXT_MAX - 6) return ap_pstrcat( cmd->pool, MSG_PREFIX, "Path argument too long to use with Rendezvous", NULL );
}
if (inPort && strcmp( inPort, "" )) {
if (strlen( inPort ) > 5)
return ap_pstrcat( cmd->pool, MSG_PREFIX, "Port argument too long", NULL );
if (!strcasecmp( inPort, "main" )) port = cmd->server->port;
else {
err = sscanf( inPort, "%d", &port );
if (!err)
return ap_pstrcat( cmd->pool, MSG_PREFIX, "Port argument not 'main' or numeric", NULL );
if (port < 0 || port > PORT_MAX)
return ap_pstrcat( cmd->pool, MSG_PREFIX, "Port argument out of range", NULL );
}
}
snprintf( txt, sizeof(txt), "path=%s", inPath );
if (!ap_configtestonly)
registerService( inName, port, txt, cmd->server );
return NULL;
}
static const command_rec rendezvousModuleCmds[] = {
{
"RegisterDefaultSite",
processRegDefaultSite,
NULL,
RSRC_CONF,
RAW_ARGS,
"Optionally, specify a port or keyword main; defaults to 80"
},
{
"RegisterUserSite",
processRegUserSite,
NULL,
RSRC_CONF,
TAKE123,
"Specify a user name or the keyword all-users or customized-users, optionally followed by keyword longname, title, or longname-title, optionally followed by a port or keyword main; default is 80"
},
{
"RegisterResource",
processRegResource,
NULL,
RSRC_CONF,
TAKE23,
"Specify a name under which to register, and a path, optionally followed by a port or keyword main; default is 80"
},
{NULL}
};
static void registerComputerName( server_rec *serverData, int inPort ) {
registerService( "", inPort, "", serverData);
return;
}
static void rendezvousChildInit( server_rec *serverData, pool *p ) {
return;
}
static void rendezvousModuleInit( server_rec *serverData, pool *p ) {
module_cfg_rec *module_cfg = getModConfig();
module_cfg->initCount++; ap_log_error( APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, serverData,
"%s Module init count=%d pid=%d.", MSG_PREFIX, module_cfg->initCount, getpid());
ap_register_cleanup( p, (void *)NULL, unregisterRefs, ap_null_cleanup );
return;
}
static void *rendezvousModuleCreateServerConfig( pool *p, char *dirspec ) {
module_cfg_rec *module_cfg;
server_cfg_rec *server_cfg;
pool *pPool;
int err;
module_cfg = ap_ctx_get( ap_global_ctx, "rendezvous_apple_module" );
if (!module_cfg) {
pPool = ap_make_sub_pool( NULL );
module_cfg = (module_cfg_rec *)ap_palloc( pPool, sizeof(module_cfg_rec) );
module_cfg->globalErr = 0;
module_cfg->pPool = pPool;
module_cfg->initCount = 0;
module_cfg->replyRecPtrArray = ap_make_array( pPool, 0, sizeof(replyRec*) );
err = pthread_mutex_init( &(module_cfg->threadMutex), NULL );
if (err)
module_cfg->globalErr = 1;
err = pthread_cond_init( &module_cfg->cond, NULL );
if (err)
module_cfg->globalErr = 1;
module_cfg->replyRunLoop = NULL;
module_cfg->initialRegReplyData = NULL;
ap_ctx_set( ap_global_ctx, "rendezvous_apple_module", module_cfg );
}
server_cfg = (server_cfg_rec *) ap_pcalloc( p, sizeof(server_cfg_rec) );
server_cfg->local = 0;
server_cfg->congenital = 0;
server_cfg->cmode = CONFIG_MODE_SERVER;
return (void *) server_cfg;
}
module MODULE_VAR_EXPORT rendezvous_apple_module = {
STANDARD_MODULE_STUFF,
rendezvousModuleInit,
rendezvousModuleCreateServerConfig,
NULL,
NULL,
NULL,
rendezvousModuleCmds,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
rendezvousChildInit,
NULL,
NULL
};