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