1 /* $NetBSD: ntservice.c,v 1.2 2020/08/11 13:15:39 christos Exp $ */ 2 3 /* $OpenLDAP$ */ 4 /* This work is part of OpenLDAP Software <http://www.openldap.org/>. 5 * 6 * Copyright 1998-2020 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.2 2020/08/11 13:15:39 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 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 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 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 /* theres been some problem with WaitForSingleObject so tell the Service 250 * Control Manager to wait 30 seconds before deploying its assasin 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 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 /* theres been some problem with WaitForSingleObject so tell the Service 292 * Control Manager to wait 30 seconds before deploying its assasin 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 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 * assasin. */ 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 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 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 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 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 * assasin */ 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 * assasin. */ 470 lutil_ServiceStatus.dwCheckPoint++; 471 lutil_ServiceStatus.dwWaitHint = THIRTY_SECONDS; 472 SetServiceStatus(hlutil_ServiceStatus, &lutil_ServiceStatus); 473 } 474 } 475 } 476 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 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 510 static char *GetLastErrorString( void ) 511 { 512 return GetErrorString( GetLastError() ); 513 } 514 #endif 515