#include <cups/http-private.h>
#include "cupsd.h"
#include <grp.h>
#ifdef __APPLE__
#include <IOKit/IOKitLib.h>
#include <IOKit/IOMessage.h>
#include <IOKit/pwr_mgt/IOPMLib.h>
#include <SystemConfiguration/SystemConfiguration.h>
#include <pthread.h>
#ifdef HAVE_DLFCN_H
# include <dlfcn.h>
#endif
#define SYSEVENT_CANSLEEP 0x1
#define SYSEVENT_WILLSLEEP 0x2
#define SYSEVENT_WOKE 0x4
#define SYSEVENT_NETCHANGED 0x8
#define SYSEVENT_NAMECHANGED 0x10
typedef struct cups_sysevent_str
{
unsigned char event;
io_connect_t powerKernelPort;
long powerNotificationID;
} cups_sysevent_t;
typedef struct cups_thread_data_str
{
cups_sysevent_t sysevent;
CFRunLoopTimerRef timerRef;
} cups_thread_data_t;
static pthread_t SysEventThread = NULL;
static pthread_mutex_t SysEventThreadMutex = { 0 };
static pthread_cond_t SysEventThreadCond = { 0 };
static CFRunLoopRef SysEventRunloop = NULL;
static CFStringRef ComputerNameKey = NULL,
NetworkGlobalKey = NULL,
HostNamesKey = NULL,
NetworkInterfaceKey = NULL;
#ifdef HAVE_DLFCN_H
static const char PSQLibPath[] = "/usr/lib/libPrintServiceQuota.dylib";
static const char PSQLibFuncName[] = "PSQUpdateQuota";
static void *PSQLibRef = NULL;
void *PSQUpdateQuotaProc = NULL;
#endif
static void *sysEventThreadEntry();
static void sysEventPowerNotifier(void *context, io_service_t service, natural_t messageType, void *messageArgument);
static void sysEventConfigurationNotifier(SCDynamicStoreRef store, CFArrayRef changedKeys, void *context);
static void sysEventTimerNotifier(CFRunLoopTimerRef timer, void *context);
#endif
void
StartServer(void)
{
#ifdef HAVE_LIBSSL
int i;
struct timeval curtime;
unsigned char data[1024];
#endif
#ifdef HAVE_LIBSSL
SSL_library_init();
SSL_load_error_strings();
gettimeofday(&curtime, NULL);
srand(curtime.tv_sec + curtime.tv_usec);
for (i = 0; i < sizeof(data); i ++)
data[i] = rand();
RAND_seed(&data, sizeof(data));
#elif defined(HAVE_GNUTLS)
gnutls_global_init();
#endif
#ifdef __APPLE__
#ifdef HAVE_DLFCN_H
if (AppleQuotas)
{
if (!PSQLibRef)
PSQLibRef = dlopen(PSQLibPath, RTLD_LAZY);
if (PSQLibRef)
PSQUpdateQuotaProc = dlsym(PSQLibRef, PSQLibFuncName);
}
#endif
#endif
StartListening();
StartBrowsing();
StartPolling();
if (cupsdOpenPipe(CGIPipes))
LogMessage(L_ERROR, "StartServer: Unable to create pipes for CGI status!");
else
{
LogMessage(L_DEBUG2, "StartServer: Adding fd %d to InputSet...", CGIPipes[0]);
FD_SET(CGIPipes[0], InputSet);
}
#ifdef __APPLE__
StartSysEventMonitor();
#endif
}
void
StopServer(void)
{
CloseAllClients();
StopListening();
StopPolling();
StopBrowsing();
#ifdef __APPLE__
StopSysEventMonitor();
#ifdef HAVE_DLFCN_H
PSQUpdateQuotaProc = NULL;
if (PSQLibRef)
{
dlclose(PSQLibRef);
PSQLibRef = NULL;
}
#endif
#endif
#if defined(HAVE_SSL) && defined(HAVE_CDSASSL)
if (ServerCertificatesArray)
{
CFRelease(ServerCertificatesArray);
ServerCertificatesArray = NULL;
}
#endif
if (CGIPipes[0] >= 0)
{
LogMessage(L_DEBUG2, "StopServer: Removing fd %d from InputSet...",
CGIPipes[0]);
FD_CLR(CGIPipes[0], InputSet);
cupsdClosePipe(CGIPipes);
}
if (AccessFile != NULL)
{
cupsFileClose(AccessFile);
AccessFile = NULL;
}
if (ErrorFile != NULL)
{
cupsFileClose(ErrorFile);
ErrorFile = NULL;
}
if (PageFile != NULL)
{
cupsFileClose(PageFile);
PageFile = NULL;
}
}
#ifdef __APPLE__
void StartSysEventMonitor(void)
{
int flags;
if (pipe(SysEventPipes))
{
LogMessage(L_EMERG, "System event monitor pipe() failed - %s!", strerror(errno));
return;
}
LogMessage(L_DEBUG2, "StartServer: Adding fd %d to InputSet...", SysEventPipes[0]);
FD_SET(SysEventPipes[0], InputSet);
flags = fcntl(SysEventPipes[0], F_GETFL, 0);
fcntl(SysEventPipes[0], F_SETFL, flags | O_NONBLOCK);
pthread_mutex_init(&SysEventThreadMutex, NULL);
pthread_cond_init(&SysEventThreadCond, NULL);
pthread_create(&SysEventThread, NULL, sysEventThreadEntry, NULL);
}
void StopSysEventMonitor(void)
{
CFRunLoopRef rl;
if (SysEventThread)
{
pthread_mutex_lock(&SysEventThreadMutex);
if (SysEventRunloop == NULL)
pthread_cond_wait(&SysEventThreadCond, &SysEventThreadMutex);
rl = SysEventRunloop;
SysEventRunloop = NULL;
pthread_mutex_unlock(&SysEventThreadMutex);
if (rl)
CFRunLoopStop(rl);
pthread_join(SysEventThread, NULL);
pthread_mutex_destroy(&SysEventThreadMutex);
pthread_cond_destroy(&SysEventThreadCond);
}
if (SysEventPipes[0] >= 0)
{
close(SysEventPipes[0]);
close(SysEventPipes[1]);
LogMessage(L_DEBUG2, "StopServer: Removing fd %d from InputSet...",
SysEventPipes[0]);
FD_CLR(SysEventPipes[0], InputSet);
SysEventPipes[0] = -1;
SysEventPipes[1] = -1;
}
}
void UpdateSysEventMonitor(void)
{
cups_sysevent_t sysevent;
printer_t *p,
*next;
while (read((int)SysEventPipes[0], &sysevent, sizeof(sysevent)) == sizeof(sysevent))
{
if ((sysevent.event & SYSEVENT_CANSLEEP))
{
for (p = Printers; p != NULL; p = p->next)
if (p->job)
break;
if (p)
{
LogMessage(L_INFO, "System sleep canceled because printer %s is active", p->name);
IOCancelPowerChange(sysevent.powerKernelPort, sysevent.powerNotificationID);
}
else
{
LogMessage(L_DEBUG, "System wants to sleep");
IOAllowPowerChange(sysevent.powerKernelPort, sysevent.powerNotificationID);
}
}
if ((sysevent.event & SYSEVENT_WILLSLEEP))
{
LogMessage(L_INFO, "System going to sleep");
Sleeping = 1;
StopAllJobs();
for (p = Printers; p != NULL; p = next)
{
next = p->next;
if (p->type & CUPS_PRINTER_REMOTE)
{
LogMessage(L_INFO, "Deleting remote destination \"%s\"", p->name);
DeletePrinter(p, 0);
}
else
{
LogMessage(L_DEBUG, "Deregistering local printer \"%s\"", p->name);
BrowseDeregisterPrinter(p, 0);
}
}
IOAllowPowerChange(sysevent.powerKernelPort, sysevent.powerNotificationID);
}
if ((sysevent.event & SYSEVENT_WOKE))
{
LogMessage(L_INFO, "System woke from sleep");
IOAllowPowerChange(sysevent.powerKernelPort, sysevent.powerNotificationID);
Sleeping = 0;
CheckJobs();
}
if ((sysevent.event & SYSEVENT_NETCHANGED))
{
if (!Sleeping)
{
LogMessage(L_DEBUG, "System network configuration changed");
NetIFUpdate(TRUE);
for (p = Printers; p != NULL; p = p->next)
p->browse_time = 0;
SendBrowseList();
}
else
LogMessage(L_DEBUG, "System network configuration changed; ignored while sleeping");
}
if ((sysevent.event & SYSEVENT_NAMECHANGED))
{
if (!Sleeping)
{
LogMessage(L_DEBUG, "Computer name changed");
for (p = Printers; p != NULL; p = p->next)
BrowseDeregisterPrinter(p, 0);
for (p = Printers; p != NULL; p = p->next)
BrowseRegisterPrinter(p);
}
else
LogMessage(L_DEBUG, "Computer name changed; ignored while sleeping");
}
}
}
static void *sysEventThreadEntry()
{
io_object_t powerNotifierObj;
IONotificationPortRef powerNotifierPort;
SCDynamicStoreRef store = NULL;
CFRunLoopSourceRef powerRLS = NULL,
storeRLS = NULL;
CFStringRef key[3],
pattern[1];
CFArrayRef keys = NULL,
patterns = NULL;
SCDynamicStoreContext storeContext;
CFRunLoopTimerContext timerContext;
cups_thread_data_t threadData;
bzero(&threadData, sizeof(threadData));
threadData.sysevent.powerKernelPort = IORegisterForSystemPower(&threadData, &powerNotifierPort, sysEventPowerNotifier, &powerNotifierObj);
if (threadData.sysevent.powerKernelPort)
{
powerRLS = IONotificationPortGetRunLoopSource(powerNotifierPort);
CFRunLoopAddSource(CFRunLoopGetCurrent(), powerRLS, kCFRunLoopDefaultMode);
}
else
DEBUG_puts("runloopThread: error registering for system power notifications");
bzero(&storeContext, sizeof(storeContext));
storeContext.info = &threadData;
store = SCDynamicStoreCreate(NULL, CFSTR("cupsd"), sysEventConfigurationNotifier, &storeContext);
if (!ComputerNameKey) ComputerNameKey = SCDynamicStoreKeyCreateComputerName(NULL);
if (!NetworkGlobalKey) NetworkGlobalKey = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL, kSCDynamicStoreDomainState, kSCEntNetIPv4);
if (!HostNamesKey) HostNamesKey = SCDynamicStoreKeyCreateHostNames(NULL);
if (!NetworkInterfaceKey) NetworkInterfaceKey= SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL, kSCDynamicStoreDomainState, kSCCompAnyRegex, kSCEntNetIPv4);
if (store && ComputerNameKey && NetworkGlobalKey && HostNamesKey && NetworkInterfaceKey)
{
key[0] = ComputerNameKey;
key[1] = NetworkGlobalKey;
key[2] = HostNamesKey;
pattern[0] = NetworkInterfaceKey;
keys = CFArrayCreate(NULL, (const void **)key, sizeof(key)/sizeof(key[0]), &kCFTypeArrayCallBacks);
patterns = CFArrayCreate(NULL, (const void **)pattern, sizeof(pattern)/sizeof(pattern[0]), &kCFTypeArrayCallBacks);
if (keys && patterns && SCDynamicStoreSetNotificationKeys(store, keys, patterns))
{
if ((storeRLS = SCDynamicStoreCreateRunLoopSource(NULL, store, 0)) != NULL)
CFRunLoopAddSource(CFRunLoopGetCurrent(), storeRLS, kCFRunLoopDefaultMode);
else
DEBUG_printf(("runloopThread: SCDynamicStoreCreateRunLoopSource failed: %s\n", SCErrorString(SCError())));
}
else
DEBUG_printf(("runloopThread: SCDynamicStoreSetNotificationKeys failed: %s\n", SCErrorString(SCError())));
}
else
DEBUG_printf(("runloopThread: SCDynamicStoreCreate failed: %s\n", SCErrorString(SCError())));
if (keys) CFRelease(keys);
if (patterns) CFRelease(patterns);
bzero(&timerContext, sizeof(timerContext));
timerContext.info = &threadData;
threadData.timerRef = CFRunLoopTimerCreate(NULL,
CFAbsoluteTimeGetCurrent() + (86400L * 365L * 10L),
86400L * 365L * 10L, 0, 0, sysEventTimerNotifier, &timerContext);
CFRunLoopAddTimer(CFRunLoopGetCurrent(), threadData.timerRef, kCFRunLoopDefaultMode);
pthread_mutex_lock(&SysEventThreadMutex);
SysEventRunloop = CFRunLoopGetCurrent();
pthread_cond_signal(&SysEventThreadCond);
pthread_mutex_unlock(&SysEventThreadMutex);
CFRunLoopRun();
if (threadData.timerRef)
{
CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), threadData.timerRef, kCFRunLoopDefaultMode);
CFRelease(threadData.timerRef);
}
if (powerRLS)
{
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), powerRLS, kCFRunLoopDefaultMode);
CFRunLoopSourceInvalidate(powerRLS);
CFRelease(powerRLS);
}
if (threadData.sysevent.powerKernelPort)
IODeregisterForSystemPower(&powerNotifierObj);
if (storeRLS)
{
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), storeRLS, kCFRunLoopDefaultMode);
CFRunLoopSourceInvalidate(storeRLS);
CFRelease(storeRLS);
}
if (store)
CFRelease(store);
pthread_exit(NULL);
}
static void sysEventPowerNotifier(void *context, io_service_t service, natural_t messageType, void *messageArgument)
{
int disposition = 1;
cups_thread_data_t *threadData;
threadData = (cups_thread_data_t *)context;
(void)service;
switch (messageType)
{
case kIOMessageCanSystemPowerOff:
case kIOMessageCanSystemSleep:
threadData->sysevent.event |= SYSEVENT_CANSLEEP;
break;
case kIOMessageSystemWillRestart:
case kIOMessageSystemWillPowerOff:
case kIOMessageSystemWillSleep:
threadData->sysevent.event |= SYSEVENT_WILLSLEEP;
break;
case kIOMessageSystemHasPoweredOn:
disposition = 2;
threadData->sysevent.event |= SYSEVENT_WOKE;
break;
case kIOMessageSystemWillNotPowerOff:
case kIOMessageSystemWillNotSleep:
case kIOMessageSystemWillPowerOn:
default:
disposition = 0;
break;
}
if (disposition == 0)
IOAllowPowerChange(threadData->sysevent.powerKernelPort, (long)messageArgument);
else
{
threadData->sysevent.powerNotificationID = (long)messageArgument;
if (disposition == 1)
{
write((int)SysEventPipes[1], &threadData->sysevent, sizeof(threadData->sysevent));
threadData->sysevent.event = 0;
}
else
{
CFRunLoopTimerSetNextFireDate(threadData->timerRef, CFAbsoluteTimeGetCurrent() + 2);
}
}
}
static void sysEventConfigurationNotifier(SCDynamicStoreRef store, CFArrayRef changedKeys, void *context)
{
(void)store;
CFRange range = CFRangeMake(0, CFArrayGetCount(changedKeys));
if (CFArrayContainsValue(changedKeys, range, ComputerNameKey))
((cups_thread_data_t *)context)->sysevent.event |= SYSEVENT_NAMECHANGED;
if (CFArrayContainsValue(changedKeys, range, NetworkGlobalKey) ||
CFArrayContainsValue(changedKeys, range, HostNamesKey) ||
CFArrayContainsValue(changedKeys, range, NetworkInterfaceKey))
((cups_thread_data_t *)context)->sysevent.event |= SYSEVENT_NETCHANGED;
CFRunLoopTimerSetNextFireDate(((cups_thread_data_t *)context)->timerRef,
CFAbsoluteTimeGetCurrent() + 2);
}
static void sysEventTimerNotifier(CFRunLoopTimerRef timer, void *context)
{
cups_thread_data_t *threadData;
threadData = (cups_thread_data_t *)context;
if (threadData->sysevent.event)
{
write((int)SysEventPipes[1], &threadData->sysevent, sizeof(threadData->sysevent));
threadData->sysevent.event = 0;
}
}
#endif