1/* $OpenLDAP$ */
2/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
3 *
4 * Copyright 1998-2011 The OpenLDAP Foundation.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted only as authorized by the OpenLDAP
9 * Public License.
10 *
11 * A copy of this license is available in the file LICENSE in the
12 * top-level directory of the distribution or, alternatively, at
13 * <http://www.OpenLDAP.org/license.html>.
14 */
15
16/*
17 * NT Service manager utilities for OpenLDAP services
18 */
19
20#include "portable.h"
21
22#ifdef HAVE_NT_SERVICE_MANAGER
23
24#include <ac/stdlib.h>
25#include <ac/string.h>
26
27#include <stdio.h>
28
29#include <windows.h>
30#include <winsvc.h>
31
32#include <ldap.h>
33
34#include "ldap_pvt_thread.h"
35
36#include "ldap_defaults.h"
37
38#include "slapdmsg.h"
39
40#define SCM_NOTIFICATION_INTERVAL	5000
41#define THIRTY_SECONDS				(30 * 1000)
42
43int	  is_NT_Service;	/* is this is an NT service? */
44
45SERVICE_STATUS			lutil_ServiceStatus;
46SERVICE_STATUS_HANDLE	hlutil_ServiceStatus;
47
48ldap_pvt_thread_cond_t	started_event,		stopped_event;
49ldap_pvt_thread_t		start_status_tid,	stop_status_tid;
50
51void (*stopfunc)(int);
52
53static char *GetLastErrorString( void );
54
55int lutil_srv_install(LPCTSTR lpszServiceName, LPCTSTR lpszDisplayName,
56		LPCTSTR lpszBinaryPathName, int auto_start)
57{
58	HKEY		hKey;
59	DWORD		dwValue, dwDisposition;
60	SC_HANDLE	schSCManager, schService;
61	char *sp = strchr( lpszBinaryPathName, ' ');
62
63	if ( sp ) *sp = '\0';
64	fprintf( stderr, "The install path is %s.\n", lpszBinaryPathName );
65	if ( sp ) *sp = ' ';
66	if ((schSCManager = OpenSCManager( NULL, NULL, SC_MANAGER_CONNECT|SC_MANAGER_CREATE_SERVICE ) ) != NULL )
67	{
68	 	if ((schService = CreateService(
69							schSCManager,
70							lpszServiceName,
71							lpszDisplayName,
72							SERVICE_ALL_ACCESS,
73							SERVICE_WIN32_OWN_PROCESS,
74							auto_start ? SERVICE_AUTO_START : SERVICE_DEMAND_START,
75							SERVICE_ERROR_NORMAL,
76							lpszBinaryPathName,
77							NULL, NULL, NULL, NULL, NULL)) != NULL)
78		{
79			char regpath[132];
80			CloseServiceHandle(schService);
81			CloseServiceHandle(schSCManager);
82
83			snprintf( regpath, sizeof regpath,
84				"SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application\\%s",
85				lpszServiceName );
86			/* Create the registry key for event logging to the Windows NT event log. */
87			if ( RegCreateKeyEx(HKEY_LOCAL_MACHINE,
88				regpath, 0,
89				"REG_SZ", REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hKey,
90				&dwDisposition) != ERROR_SUCCESS)
91			{
92				fprintf( stderr, "RegCreateKeyEx() failed. GetLastError=%lu (%s)\n", GetLastError(), GetLastErrorString() );
93				RegCloseKey(hKey);
94				return(0);
95			}
96			if ( sp ) *sp = '\0';
97			if ( RegSetValueEx(hKey, "EventMessageFile", 0, REG_EXPAND_SZ, lpszBinaryPathName, strlen(lpszBinaryPathName) + 1) != ERROR_SUCCESS)
98			{
99				fprintf( stderr, "RegSetValueEx(EventMessageFile) failed. GetLastError=%lu (%s)\n", GetLastError(), GetLastErrorString() );
100				RegCloseKey(hKey);
101				return(0);
102			}
103
104			dwValue = EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE | EVENTLOG_INFORMATION_TYPE;
105			if ( RegSetValueEx(hKey, "TypesSupported", 0, REG_DWORD, (LPBYTE) &dwValue, sizeof(DWORD)) != ERROR_SUCCESS)
106			{
107				fprintf( stderr, "RegCreateKeyEx(TypesSupported) failed. GetLastError=%lu (%s)\n", GetLastError(), GetLastErrorString() );
108				RegCloseKey(hKey);
109				return(0);
110			}
111			RegCloseKey(hKey);
112			return(1);
113		}
114		else
115		{
116			fprintf( stderr, "CreateService() failed. GetLastError=%lu (%s)\n", GetLastError(), GetLastErrorString() );
117			CloseServiceHandle(schSCManager);
118			return(0);
119		}
120	}
121	else
122		fprintf( stderr, "OpenSCManager() failed. GetLastError=%lu (%s)\n", GetLastError(), GetLastErrorString() );
123	return(0);
124}
125
126
127int lutil_srv_remove(LPCTSTR lpszServiceName, LPCTSTR lpszBinaryPathName)
128{
129	SC_HANDLE schSCManager, schService;
130
131	fprintf( stderr, "The installed path is %s.\n", lpszBinaryPathName );
132	if ((schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT|SC_MANAGER_CREATE_SERVICE)) != NULL )
133	{
134	 	if ((schService = OpenService(schSCManager, lpszServiceName, DELETE)) != NULL)
135		{
136			if ( DeleteService(schService) == TRUE)
137			{
138				CloseServiceHandle(schService);
139				CloseServiceHandle(schSCManager);
140				return(1);
141			} else {
142				fprintf( stderr, "DeleteService() failed. GetLastError=%lu (%s)\n", GetLastError(), GetLastErrorString() );
143				fprintf( stderr, "The %s service has not been removed.\n", lpszBinaryPathName);
144				CloseServiceHandle(schService);
145				CloseServiceHandle(schSCManager);
146				return(0);
147			}
148		} else {
149			fprintf( stderr, "OpenService() failed. GetLastError=%lu (%s)\n", GetLastError(), GetLastErrorString() );
150			CloseServiceHandle(schSCManager);
151			return(0);
152		}
153	}
154	else
155		fprintf( stderr, "OpenSCManager() failed. GetLastError=%lu (%s)\n", GetLastError(), GetLastErrorString() );
156	return(0);
157}
158
159
160#if 0 /* unused */
161DWORD
162svc_installed (LPTSTR lpszServiceName, LPTSTR lpszBinaryPathName)
163{
164	char buf[256];
165	HKEY key;
166	DWORD rc;
167	DWORD type;
168	long len;
169
170	strcpy(buf, TEXT("SYSTEM\\CurrentControlSet\\Services\\"));
171	strcat(buf, lpszServiceName);
172	if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, buf, 0, KEY_QUERY_VALUE, &key) != ERROR_SUCCESS)
173		return(-1);
174
175	rc = 0;
176	if (lpszBinaryPathName) {
177		len = sizeof(buf);
178		if (RegQueryValueEx(key, "ImagePath", NULL, &type, buf, &len) == ERROR_SUCCESS) {
179			if (strcmp(lpszBinaryPathName, buf))
180				rc = -1;
181		}
182	}
183	RegCloseKey(key);
184	return(rc);
185}
186
187
188DWORD
189svc_running (LPTSTR lpszServiceName)
190{
191	SC_HANDLE service;
192	SC_HANDLE scm;
193	DWORD rc;
194	SERVICE_STATUS ss;
195
196	if (!(scm = OpenSCManager(NULL, NULL, GENERIC_READ)))
197		return(GetLastError());
198
199	rc = 1;
200	service = OpenService(scm, lpszServiceName, SERVICE_QUERY_STATUS);
201	if (service) {
202		if (!QueryServiceStatus(service, &ss))
203			rc = GetLastError();
204		else if (ss.dwCurrentState != SERVICE_STOPPED)
205			rc = 0;
206		CloseServiceHandle(service);
207	}
208	CloseServiceHandle(scm);
209	return(rc);
210}
211#endif
212
213static void *start_status_routine( void *ptr )
214{
215	DWORD	wait_result;
216	int		done = 0;
217
218	while ( !done )
219	{
220		wait_result = WaitForSingleObject( started_event, SCM_NOTIFICATION_INTERVAL );
221		switch ( wait_result )
222		{
223			case WAIT_ABANDONED:
224			case WAIT_OBJECT_0:
225				/* the object that we were waiting for has been destroyed (ABANDONED) or
226				 * signalled (TIMEOUT_0). We can assume that the startup process is
227				 * complete and tell the Service Control Manager that we are now runnng */
228				lutil_ServiceStatus.dwCurrentState	= SERVICE_RUNNING;
229				lutil_ServiceStatus.dwWin32ExitCode	= NO_ERROR;
230				lutil_ServiceStatus.dwCheckPoint++;
231				lutil_ServiceStatus.dwWaitHint		= 1000;
232				SetServiceStatus(hlutil_ServiceStatus, &lutil_ServiceStatus);
233				done = 1;
234				break;
235			case WAIT_TIMEOUT:
236				/* We've waited for the required time, so send an update to the Service Control
237				 * Manager saying to wait again. */
238				lutil_ServiceStatus.dwCheckPoint++;
239				lutil_ServiceStatus.dwWaitHint = SCM_NOTIFICATION_INTERVAL * 2;
240				SetServiceStatus(hlutil_ServiceStatus, &lutil_ServiceStatus);
241				break;
242			case WAIT_FAILED:
243				/* theres been some problem with WaitForSingleObject so tell the Service
244				 * Control Manager to wait 30 seconds before deploying its assasin and
245				 * then leave the thread. */
246				lutil_ServiceStatus.dwCheckPoint++;
247				lutil_ServiceStatus.dwWaitHint = THIRTY_SECONDS;
248				SetServiceStatus(hlutil_ServiceStatus, &lutil_ServiceStatus);
249				done = 1;
250				break;
251		}
252	}
253	ldap_pvt_thread_exit(NULL);
254	return NULL;
255}
256
257
258
259static void *stop_status_routine( void *ptr )
260{
261	DWORD	wait_result;
262	int		done = 0;
263
264	while ( !done )
265	{
266		wait_result = WaitForSingleObject( stopped_event, SCM_NOTIFICATION_INTERVAL );
267		switch ( wait_result )
268		{
269			case WAIT_ABANDONED:
270			case WAIT_OBJECT_0:
271				/* the object that we were waiting for has been destroyed (ABANDONED) or
272				 * signalled (TIMEOUT_0). The shutting down process is therefore complete
273				 * and the final SERVICE_STOPPED message will be sent to the service control
274				 * manager prior to the process terminating. */
275				done = 1;
276				break;
277			case WAIT_TIMEOUT:
278				/* We've waited for the required time, so send an update to the Service Control
279				 * Manager saying to wait again. */
280				lutil_ServiceStatus.dwCheckPoint++;
281				lutil_ServiceStatus.dwWaitHint = SCM_NOTIFICATION_INTERVAL * 2;
282				SetServiceStatus(hlutil_ServiceStatus, &lutil_ServiceStatus);
283				break;
284			case WAIT_FAILED:
285				/* theres been some problem with WaitForSingleObject so tell the Service
286				 * Control Manager to wait 30 seconds before deploying its assasin and
287				 * then leave the thread. */
288				lutil_ServiceStatus.dwCheckPoint++;
289				lutil_ServiceStatus.dwWaitHint = THIRTY_SECONDS;
290				SetServiceStatus(hlutil_ServiceStatus, &lutil_ServiceStatus);
291				done = 1;
292				break;
293		}
294	}
295	ldap_pvt_thread_exit(NULL);
296	return NULL;
297}
298
299
300
301static void WINAPI lutil_ServiceCtrlHandler( IN DWORD Opcode)
302{
303	switch (Opcode)
304	{
305	case SERVICE_CONTROL_STOP:
306	case SERVICE_CONTROL_SHUTDOWN:
307
308		lutil_ServiceStatus.dwCurrentState	= SERVICE_STOP_PENDING;
309		lutil_ServiceStatus.dwCheckPoint++;
310		lutil_ServiceStatus.dwWaitHint		= SCM_NOTIFICATION_INTERVAL * 2;
311		SetServiceStatus(hlutil_ServiceStatus, &lutil_ServiceStatus);
312
313		ldap_pvt_thread_cond_init( &stopped_event );
314		if ( stopped_event == NULL )
315		{
316			/* the event was not created. We will ask the service control manager for 30
317			 * seconds to shutdown */
318			lutil_ServiceStatus.dwCheckPoint++;
319			lutil_ServiceStatus.dwWaitHint		= THIRTY_SECONDS;
320			SetServiceStatus(hlutil_ServiceStatus, &lutil_ServiceStatus);
321		}
322		else
323		{
324			/* start a thread to report the progress to the service control manager
325			 * until the stopped_event is fired. */
326			if ( ldap_pvt_thread_create( &stop_status_tid, 0, stop_status_routine, NULL ) == 0 )
327			{
328
329			}
330			else {
331				/* failed to create the thread that tells the Service Control Manager that the
332				 * service stopping is proceeding.
333				 * tell the Service Control Manager to wait another 30 seconds before deploying its
334				 * assasin.  */
335				lutil_ServiceStatus.dwCheckPoint++;
336				lutil_ServiceStatus.dwWaitHint = THIRTY_SECONDS;
337				SetServiceStatus(hlutil_ServiceStatus, &lutil_ServiceStatus);
338			}
339		}
340		stopfunc( -1 );
341		break;
342
343	case SERVICE_CONTROL_INTERROGATE:
344		SetServiceStatus(hlutil_ServiceStatus, &lutil_ServiceStatus);
345		break;
346	}
347	return;
348}
349
350void *lutil_getRegParam( char *svc, char *value )
351{
352	HKEY hkey;
353	char path[255];
354	DWORD vType;
355	static char vValue[1024];
356	DWORD valLen = sizeof( vValue );
357
358	if ( svc != NULL )
359		snprintf ( path, sizeof path, "SOFTWARE\\%s", svc );
360	else
361		snprintf ( path, sizeof path, "SOFTWARE\\OpenLDAP\\Parameters" );
362
363	if ( RegOpenKeyEx( HKEY_LOCAL_MACHINE, path, 0, KEY_READ, &hkey ) != ERROR_SUCCESS )
364	{
365		return NULL;
366	}
367
368	if ( RegQueryValueEx( hkey, value, NULL, &vType, vValue, &valLen ) != ERROR_SUCCESS )
369	{
370		RegCloseKey( hkey );
371		return NULL;
372	}
373	RegCloseKey( hkey );
374
375	switch ( vType )
376	{
377	case REG_BINARY:
378	case REG_DWORD:
379		return (void*)&vValue;
380	case REG_SZ:
381		return (void*)&vValue;
382	}
383	return (void*)NULL;
384}
385
386void lutil_LogStartedEvent( char *svc, int slap_debug, char *configfile, char *urls )
387{
388	char *Inserts[5];
389	WORD i = 0, j;
390	HANDLE hEventLog;
391
392	hEventLog = RegisterEventSource( NULL, svc );
393
394	Inserts[i] = (char *)malloc( 20 );
395	itoa( slap_debug, Inserts[i++], 10 );
396	Inserts[i++] = strdup( configfile );
397	Inserts[i++] = strdup( urls ? urls : "ldap:///" );
398
399	ReportEvent( hEventLog, EVENTLOG_INFORMATION_TYPE, 0,
400		MSG_SVC_STARTED, NULL, i, 0, (LPCSTR *) Inserts, NULL );
401
402	for ( j = 0; j < i; j++ )
403		ldap_memfree( Inserts[j] );
404	DeregisterEventSource( hEventLog );
405}
406
407
408
409void lutil_LogStoppedEvent( char *svc )
410{
411	HANDLE hEventLog;
412
413	hEventLog = RegisterEventSource( NULL, svc );
414	ReportEvent( hEventLog, EVENTLOG_INFORMATION_TYPE, 0,
415		MSG_SVC_STOPPED, NULL, 0, 0, NULL, NULL );
416	DeregisterEventSource( hEventLog );
417}
418
419
420void lutil_CommenceStartupProcessing( char *lpszServiceName,
421							   void (*stopper)(int) )
422{
423	hlutil_ServiceStatus = RegisterServiceCtrlHandler( lpszServiceName, (LPHANDLER_FUNCTION)lutil_ServiceCtrlHandler);
424
425	stopfunc = stopper;
426
427	/* initialize the Service Status structure */
428	lutil_ServiceStatus.dwServiceType				= SERVICE_WIN32_OWN_PROCESS;
429	lutil_ServiceStatus.dwCurrentState				= SERVICE_START_PENDING;
430	lutil_ServiceStatus.dwControlsAccepted			= SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
431	lutil_ServiceStatus.dwWin32ExitCode				= NO_ERROR;
432	lutil_ServiceStatus.dwServiceSpecificExitCode	= 0;
433	lutil_ServiceStatus.dwCheckPoint					= 1;
434	lutil_ServiceStatus.dwWaitHint					= SCM_NOTIFICATION_INTERVAL * 2;
435
436	SetServiceStatus(hlutil_ServiceStatus, &lutil_ServiceStatus);
437
438	/* start up a thread to keep sending SERVICE_START_PENDING to the Service Control Manager
439	 * until the slapd listener is completed and listening. Only then should we send
440	 * SERVICE_RUNNING to the Service Control Manager. */
441	ldap_pvt_thread_cond_init( &started_event );
442	if ( started_event == NULL)
443	{
444		/* failed to create the event to determine when the startup process is complete so
445		 * tell the Service Control Manager to wait another 30 seconds before deploying its
446		 * assasin  */
447		lutil_ServiceStatus.dwCheckPoint++;
448		lutil_ServiceStatus.dwWaitHint = THIRTY_SECONDS;
449		SetServiceStatus(hlutil_ServiceStatus, &lutil_ServiceStatus);
450	}
451	else
452	{
453		/* start a thread to report the progress to the service control manager
454		 * until the started_event is fired.  */
455		if ( ldap_pvt_thread_create( &start_status_tid, 0, start_status_routine, NULL ) == 0 )
456		{
457
458		}
459		else {
460			/* failed to create the thread that tells the Service Control Manager that the
461			 * service startup is proceeding.
462			 * tell the Service Control Manager to wait another 30 seconds before deploying its
463			 * assasin.  */
464			lutil_ServiceStatus.dwCheckPoint++;
465			lutil_ServiceStatus.dwWaitHint = THIRTY_SECONDS;
466			SetServiceStatus(hlutil_ServiceStatus, &lutil_ServiceStatus);
467		}
468	}
469}
470
471void lutil_ReportShutdownComplete(  )
472{
473	if ( is_NT_Service )
474	{
475		/* stop sending SERVICE_STOP_PENDING messages to the Service Control Manager */
476		ldap_pvt_thread_cond_signal( &stopped_event );
477		ldap_pvt_thread_cond_destroy( &stopped_event );
478
479		/* wait for the thread sending the SERVICE_STOP_PENDING messages to the Service Control Manager to die.
480		 * if the wait fails then put ourselves to sleep for half the Service Control Manager update interval */
481		if (ldap_pvt_thread_join( stop_status_tid, (void *) NULL ) == -1)
482			ldap_pvt_thread_sleep( SCM_NOTIFICATION_INTERVAL / 2 );
483
484		lutil_ServiceStatus.dwCurrentState = SERVICE_STOPPED;
485		lutil_ServiceStatus.dwCheckPoint++;
486		lutil_ServiceStatus.dwWaitHint = SCM_NOTIFICATION_INTERVAL;
487		SetServiceStatus(hlutil_ServiceStatus, &lutil_ServiceStatus);
488	}
489}
490
491static char *GetErrorString( int err )
492{
493	static char msgBuf[1024];
494
495	FormatMessage(
496		FORMAT_MESSAGE_FROM_SYSTEM,
497		NULL,
498		err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
499		msgBuf, 1024, NULL );
500
501	return msgBuf;
502}
503
504static char *GetLastErrorString( void )
505{
506	return GetErrorString( GetLastError() );
507}
508#endif
509