/* * $Id: //websites/unixwiz/unixwiz.net/webroot/tools/dbmutex-1.0.1.cpp#1 $ * * DBWinMutex Creation Service * * written by : Stephen J. Friedl * Software Consultant * Tustin, California USA * http://www.unixwiz.net/tools/ * * This program is a very simple Win32 service that creates and holds * the DBWinMutex object that's used by the OutputDebugString() API * function. It's normally created as needed with permissions that * allow anybody to use it, but it turns out that if a non-admin * indirectly calls CreateMutex() when the mutex had already been * created, the existing one is opened with MUTEX_ALL_ACCESS rights. * It fails, and makes OutputDebugString() unavailable. This is * >really< annoying. * * So, this DBMutex service launches at boot time, and it creates the * mutex with wide-open permissions that permit anybody to open * with MUTEX_ALL_ACCESS rights (or: if the mutex had already been * created, it adjusts the permissions of the one that's there). * * Then it just sits until told to stop or shutdown: it uses ZERO * CPU time while "running", and it shuts down very quickly. * * It's possible to work around this with a simple application * program run by hand, but it requires too much intervention: * we prefer to just set up the mutex correctly and leave it * alone. * * * COMMAND LINE * ------------ * * The service can be installed from the command line by placing * this executable where it should run from (C:\BIN ?), and then * * C> cd \bin * C> dbmutex -install * * to install the service with a manual start, or * * C> dbmutex -installauto * * to install with auto-start at boot. Neither of these actually * launches the service. * * Once installed, the service can be started or stopped from the * command line with: (case doesn't matter) * * C> NET START DBMUTEX * C> NET STOP DBMUTEX * * COMPILE THIS * ------------ * * We do all our Win32 development at the command line - really - * so you'll need to get your MS Visual C working this way (look * for the MSVCVARS.BAT batch file). * * This done, compile and link: * * cl /c /Gz /W4 /GA /GF /MD dbmutex.cpp * * link /out:dbmutex.exe dbmutex.obj advapi32.lib user32.lib * * Now "dbmutex.exe" is available for installation. * * REVISION HISTORY * * 1.0.1 - 2003/12/10 * * Initial release */ #define STRICT #define WIN32_LEAN_AND_MEAN #define _WIN32_WINNT 0x0400 /* build for NT 4.0 and higher */ /* required to get MB_SERVICE_NOTIFICATION */ #include #include #include #include #include static const TCHAR Version[] = _T("dbmutex 1.0.1 - 2003/12/10 - http://www.unixwiz.net/tools/"); /*------------------------------------------------------------------------ * CONFIGURATION / PORTABILITY / LINT STUFF * * ASIZE: given an array (string, array, whatever), return the # of * ITEMS in the list. * * UNUSED_PARAMETER(x) - suppresses warnings about params not used */ #define ASIZE(x) (sizeof(x) / sizeof((x)[0])) #define UNUSED_PARAMETER(x) ((void)(x)) /*------------------------------------------------------------------------ * IMPORTANT NAMES * * These three strings define the name and description of the service, * plus the key mutex that we're holding open. The name of the service * can be changed, but the mutex MUST be "DBWinMutex" or it won't match * the behavior of the OutputDebugString() API. */ static TCHAR ServiceName[] = _T("DBMutex"); static TCHAR ServiceDesc[] = _T("DBWinMutex Creation Service"); static const TCHAR mutexName[] = _T("DBWinMutex"); static HANDLE hStopEvent = NULL; static HANDLE hMutex = NULL; static SERVICE_STATUS sStatus; // current status of the service static SERVICE_STATUS_HANDLE hStatusHandle; // internal function prototypes static VOID WINAPI service_main(DWORD dwArgc, LPTSTR *lpszArgv); static VOID install_service(DWORD dwStartType); static VOID remove_service(void); static BOOL ReportStatusToSCM(DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwWaitHint); static const TCHAR *printable_servicecontrol(DWORD dwControl); static const TCHAR *printable_state(DWORD dwState); static const TCHAR *printable_starttype(DWORD dwStartType); static const TCHAR *prterr(DWORD err); static const TCHAR *prterr(void); #ifdef _UNICODE /*lint -wprintf(1, logprintf, popupbox) */ #else /*lint -printf(1, logprintf, popupbox) */ #endif static void __cdecl logprintf(const TCHAR *format, ...); static void __cdecl popupbox(const TCHAR *format, ...); int __cdecl _tmain(int argc, TCHAR **argv) { /*---------------------------------------------------------------- * RUNNING AS A SERVICE? * * This program can be invoked from the command line (for install * and remove), or from the services control manager (in the normal * case), and it's helpful to know which of the two we're at. * * We've found that three NULL handles is sufficient to suggest * that were under the SCM. */ if ( GetStdHandle(STD_INPUT_HANDLE ) == 0 || GetStdHandle(STD_OUTPUT_HANDLE) == 0 || GetStdHandle(STD_ERROR_HANDLE ) == 0 ) { static const SERVICE_TABLE_ENTRY dispatchTable[] = { { ServiceName, service_main }, { NULL, NULL } /* ENDMARKER */ }; if ( ! StartServiceCtrlDispatcher(dispatchTable) ) { logprintf(_T("StartServiceCtrlDispatcher() failed [%s]"), prterr() ); return EXIT_FAILURE; } return EXIT_SUCCESS; } /*---------------------------------------------------------------- * USER MODE STUFF */ for ( int i = 1; i < argc; i++ ) { TCHAR *arg = argv[i]; if ( arg[0] == '-' || arg[0] == '/' ) { #define MATCHARG(word) (_tcsicmp(arg+1, _T(word)) == 0 ) if ( MATCHARG("install") ) { install_service(SERVICE_DEMAND_START); return EXIT_SUCCESS; } else if ( MATCHARG("installauto") ) { install_service(SERVICE_AUTO_START); return EXIT_SUCCESS; } else if ( MATCHARG("remove") ) { remove_service(); return EXIT_SUCCESS; } else if ( MATCHARG("version") || MATCHARG("-version")) { _putts(Version); return EXIT_SUCCESS; } } _tprintf( _T("ERROR: unknown param {%s}\n"), arg); } ; _tprintf( _T("%s\n\n"), Version ); _tprintf( _T("USAGE: %s [-install|-installauto|-remove]\n"), argv[0]); _tprintf( _T(" -install Install service: manual start\n") ); _tprintf( _T(" -installauto Install service: auto start\n") ); _tprintf( _T(" -remove Stop service & remove it\n") ); return EXIT_FAILURE; } /* * service_ctrl() * * Given a control word (stop, pause, etc.), // // FUNCTION: service_ctrl // // PURPOSE: This function is called by the SCM whenever // ControlService() is called on this service. // // PARAMETERS: // dwCtrlCode - type of control requested // // RETURN VALUE: // none // // COMMENTS: // */ static VOID WINAPI service_ctrl(DWORD dwCtrlCode) { logprintf( _T("Received service control %s"), printable_servicecontrol(dwCtrlCode) ); switch (dwCtrlCode) { case SERVICE_CONTROL_SHUTDOWN: case SERVICE_CONTROL_STOP: sStatus.dwCurrentState = SERVICE_STOP_PENDING; if ( hStopEvent ) { logprintf("setting the Stop Event"); SetEvent(hStopEvent); } break; case SERVICE_CONTROL_INTERROGATE: /* nothing? */ break; default: break; } ReportStatusToSCM(sStatus.dwCurrentState, NO_ERROR, 0); } /* * service_main() * * This is the real entry point to the service, and it's called by * the service control manager with SERVICE_START. */ static void WINAPI service_main(DWORD dwArgc, LPTSTR *lpszArgv) { UNUSED_PARAMETER(dwArgc); UNUSED_PARAMETER(lpszArgv); /*---------------------------------------------------------------- * TELL THE CONTROLLER ABOUT US * * The controller only knew enough to start us, but we have to * talk back *to it* so we can update our current status. This * status handle is vital. * * NOTE: the MSDN docs say that RegisterServiceCtrlHandler() has * been replaced with ...Ex(), but this would cut NT4 out of the * picture and we would rather not do that. */ hStatusHandle = RegisterServiceCtrlHandler(ServiceName, service_ctrl); if ( ! hStatusHandle ) { logprintf(_T("Cannot RegisterServiceCtrlHandler(%s) [%s]"), ServiceName, prterr() ); return; } ZeroMemory(&sStatus, sizeof sStatus); sStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; sStatus.dwCurrentState = SERVICE_START_PENDING; sStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN; sStatus.dwWin32ExitCode = 0; sStatus.dwServiceSpecificExitCode = 0; sStatus.dwCheckPoint = 0; BOOL bError = TRUE; __try { /*-------------------------------------------------------- * This tells the service control manager that we're just * starting up and to expect to hear a bit more from us * within 500 msec or so. */ if ( ! ReportStatusToSCM(SERVICE_START_PENDING, NO_ERROR, 500)) __leave; /*-------------------------------------------------------- * Create the stop event object that the main thread will * use to tell us to shut down. It has no name, and we * don't allow it to be inherited. */ if ( (hStopEvent = CreateEvent(NULL, FALSE, FALSE, NULL)) == 0) { logprintf( _T("CreateEvent() failed [%s]"), prterr() ); __leave; } /*--------------------------------------------------------- * CREATE THE MUTEX * * This is the whole reason for this service: we create a * mutex that regular users can open with MUTEX_ALL_ACCESS. * This SECURITY_DESCRIPTOR is used again shortly in the * event that the object previously existed. */ SECURITY_DESCRIPTOR sd_wideopen; InitializeSecurityDescriptor(&sd_wideopen, SECURITY_DESCRIPTOR_REVISION); SetSecurityDescriptorDacl( &sd_wideopen, // addr of SD TRUE, // TRUE=DACL present NULL, // ... but it's empty (wide open) FALSE); // DACL explicitly set, not defaulted SECURITY_ATTRIBUTES sa; ZeroMemory(&sa, sizeof sa); sa.nLength = sizeof sa; sa.lpSecurityDescriptor = &sd_wideopen; sa.bInheritHandle = FALSE; if ( (hMutex = CreateMutex(&sa, FALSE, mutexName)) == 0) { popupbox( _T("ERROR: can't create %s mutex [%s]"), mutexName, prterr() ); logprintf( _T("Cannot CreateMutex [%s]"), prterr() ); __leave; } /*-------------------------------------------------------- * ENSURE SECURITY DESCRIPTOR IS RIGHT * * If CreateMutex() sets the error to ERROR_ALREADY_EXISTS, * we have to proactively go and change the DACL of the * object that was created by another. */ if ( GetLastError() == ERROR_ALREADY_EXISTS && ! SetKernelObjectSecurity( hMutex, DACL_SECURITY_INFORMATION, &sd_wideopen ) ) { logprintf( _T("Cannot SetKOSecurity [%s]"), prterr() ); /* ERROR: this is a serious problem? */ } /*-------------------------------------------------------- * IT WORKS! * * Now we're running, and our two objects have been created * successfully. Tell the service control manager that we * are done with the startup process. */ if ( ! ReportStatusToSCM(SERVICE_RUNNING, NO_ERROR, 0)) { /* ERROR: can't tell SCM that we're running */ __leave; } bError = FALSE; (void) WaitForSingleObject(hStopEvent, INFINITE); __leave; } __finally { if (hStopEvent) CloseHandle(hStopEvent), hStopEvent = 0; if (hMutex) CloseHandle(hMutex), hMutex = 0; if (bError) { logprintf(_T("Service failed - stopping") ); } } ReportStatusToSCM(SERVICE_STOPPED, !!bError, 0); } /* * ReportStatusToSCM() * * Report to the Service Control Manager how we're doing. This * exists mainly to set the dwCurrentState (running, stopping, * etc.) but to provide bb * Tell the Service Control Manager how we're doing. The * current state is really */ static BOOL ReportStatusToSCM(DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwWaitHint) { static DWORD dwCheckPoint = 1; logprintf( _T("ReportStatusToSCM(%s, exit=%ld, hint=%ld"), printable_state(dwCurrentState), dwWin32ExitCode, dwWaitHint); if (dwCurrentState == SERVICE_START_PENDING) sStatus.dwControlsAccepted = 0; else sStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP; sStatus.dwCurrentState = dwCurrentState; sStatus.dwWin32ExitCode = dwWin32ExitCode; sStatus.dwWaitHint = dwWaitHint; if ( dwCurrentState == SERVICE_RUNNING || dwCurrentState == SERVICE_STOPPED ) { sStatus.dwCheckPoint = 0; } else { sStatus.dwCheckPoint = dwCheckPoint++; } // Report the status of the service to the service control manager. const BOOL fResult = SetServiceStatus( hStatusHandle, &sStatus); if ( !fResult ) { logprintf( _T("SetServiceStatus failed [%s]"), prterr() ); } return fResult; } /* * logprintf() * * This sends a message to the event log and/or to the debugging * interface. The message itself is printf-like. */ static void __cdecl logprintf(const TCHAR *format, ...) { TCHAR msgbuf[256], *p = msgbuf, *pmax = msgbuf + ASIZE(msgbuf) - 3; /* CR+LF+NUL */ p += _stprintf(p, _T("%s: "), ServiceName); va_list args; va_start(args, format); p += _vsntprintf(p, (int)(pmax - p), format, args); va_end(args); /* delete trailing whitespace */ while ( p > msgbuf && _istspace(p[-1])) *--p = '\0'; p[0] = '\n'; p[1] = '\0'; OutputDebugString(msgbuf); #if 0 p[0] = '\0'; HANDLE hEventSource = RegisterEventSource( NULL, // server ServiceName); // service const TCHAR *strings[1] = { msgbuf }; if (hEventSource != NULL) { ReportEvent(hEventSource, // handle of event source EVENTLOG_ERROR_TYPE, // event type 0, // event category 0, // event ID NULL, // current user's SID 1, // strings in lpszStrings 0, // no bytes of raw data strings, // array of error strings NULL); // no raw data DeregisterEventSource(hEventSource); } #endif } /*------------------------------------------------------------------------ * INSTALL AND REMOVE SUPPORT * * Strictly speaking, the service EXE need not contain the code for * installing and removing the service, but it sure is convenient * to do so. */ /* * open_scm() * * This attempts to connect to the Service Controller Manager (with * given access mask), and if we're not able to do it, it exits * with failure. This is really only a helper... */ static SC_HANDLE open_scm(DWORD dwAccess) { SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, dwAccess); if ( hSCManager == 0 ) { _tprintf( _T("ERROR: cannot OpenScManager [err=%ld]\n"), GetLastError() ); exit(EXIT_FAILURE); } return hSCManager; } /* * install_service() * * Install the service so it launches with Windows. The EXE is * run from the *current location*, so be sure to park the * binary where it will ultimately live. * * REMINDER: this runs in user space, not service space. */ static void install_service(DWORD dwStartType) { /*---------------------------------------------------------------- * FIND OUR OWN PATH * * Figure out where this executable is located: this is where * the service manager will later look for it to run it. */ TCHAR szPath[512]; if ( GetModuleFileName( NULL, szPath, ASIZE(szPath) ) == 0 ) { _tprintf(_T("Unable to install %s - %s\n"), ServiceName, prterr( GetLastError() ) ); exit(EXIT_FAILURE); } SC_HANDLE hSCManager = open_scm(SC_MANAGER_CREATE_SERVICE); SC_HANDLE hService = CreateService( hSCManager, ServiceName, // name of service ServiceDesc, // name to display SERVICE_ALL_ACCESS, // desired access SERVICE_WIN32_OWN_PROCESS, // service type dwStartType, // start type SERVICE_ERROR_NORMAL, // error control type szPath, // service's binary NULL, // no load order grp NULL, // no tag identifier _T(""), // dependencies NULL, // LocalSystem account NULL); // no password if ( hService == 0 ) { _tprintf(_T("ERROR: cannot CreateService(%s) [%s]\n"), ServiceName, prterr() ); } else { CloseServiceHandle(hService); _tprintf(_T("Service %s installed (%s)\n"), ServiceName, printable_starttype(dwStartType) ); } CloseServiceHandle(hSCManager); } /* * remove_service() * * Stop the service (if running), then remove it from the * services database. * * REMINDER: this runs in user space, not service space. */ static void remove_service(void) { SC_HANDLE hSCManager = open_scm( 0 ); /* odd access mask: hmmm */ SC_HANDLE hService = 0; __try { const DWORD dwAccess = DELETE | SERVICE_QUERY_STATUS | SERVICE_STOP; if ( (hService = OpenService(hSCManager, ServiceName, dwAccess)) == 0) { _tprintf(_T("OpenService(%s) failed - %s\n"), ServiceName, prterr() ); __leave; } /* don't need this any more */ CloseServiceHandle(hSCManager); hSCManager = 0; /*-------------------------------------------------------- * TRY TO STOP THE SERVICE * * This kicks the service to get it shutting down, but * also waits until it *has* shut down (or at least when * the SCM has given up). * * We suspect there are some timing issues going on here * that could be heavily tuned, but we don't know what * they are. Our DBMutex service stops so quickly that we * really don't need to wait all that long anyway. */ SERVICE_STATUS sStat; if ( ControlService( hService, SERVICE_CONTROL_STOP, &sStat) ) { _tprintf(_T("Stopping %s."), ServiceName); do { Sleep(100); _tprintf( _T(".") ); } while ( QueryServiceStatus( hService, &sStat ) && sStat.dwCurrentState == SERVICE_STOP_PENDING ); _tprintf( _T("\nService %s "), ServiceName); if ( sStat.dwCurrentState == SERVICE_STOPPED ) _tprintf( _T("stopped as requested\n") ); else _tprintf( _T("failed to stop (state=%s)\n"), printable_state(sStat.dwCurrentState)); } /*-------------------------------------------------------- * REMOVE THE SERVICE * * At this point we're pretty sure it's stopped (at least * we tried), so delete it. */ if ( DeleteService(hService) ) _tprintf( _T("%s removed.\n"), ServiceName); else _tprintf( _T("DeleteService(%s) failed - %s\n"), ServiceName, prterr() ); } __finally { if (hService) CloseServiceHandle(hService); if (hSCManager) CloseServiceHandle(hSCManager); } } /* * popupbox() * * If this service is not able to start, we can pop up a message * to the console to identify it. It uses a printf-style parameter * list, and there is no return value (e.g., we don */ static void __cdecl popupbox(const TCHAR *format, ...) { TCHAR msgbuf[256], *p = msgbuf, *pmax = msgbuf + ASIZE(msgbuf) - 1; va_list args; va_start(args, format); p += _vsntprintf(p, (int)(pmax -p), format, args); va_end(args); UINT uType = MB_OK | MB_SERVICE_NOTIFICATION; MessageBox( (HWND) 0, msgbuf, _T("DBWinMutex Service"), uType); } /*------------------------------------------------------------------------ * PRINTABLE HELPER STUFF * * When trying to report what we're doing, it's helpful to convert * the internal macro numbers to printable names: these functions * here are for that purpose. */ #define WORDCASE(w) case w: return _T(#w) /* * printable_state() * * Given a state that we're transitioning to, return a printable * version of it. This is only for reporting purposes. */ static const TCHAR *printable_state(DWORD dwState) { switch (dwState) { WORDCASE(SERVICE_STOPPED); WORDCASE(SERVICE_START_PENDING); WORDCASE(SERVICE_STOP_PENDING); WORDCASE(SERVICE_RUNNING); WORDCASE(SERVICE_CONTINUE_PENDING); WORDCASE(SERVICE_PAUSE_PENDING); WORDCASE(SERVICE_PAUSED); default: return _T("SERVICE_???"); } } static const TCHAR *printable_starttype(DWORD dwStartType) { switch (dwStartType) { WORDCASE(SERVICE_AUTO_START); WORDCASE(SERVICE_BOOT_START); WORDCASE(SERVICE_DEMAND_START); WORDCASE(SERVICE_DISABLED); WORDCASE(SERVICE_SYSTEM_START); default: return _T("SERVICE_???"); } } static const TCHAR *printable_servicecontrol(DWORD dwControl) { switch (dwControl) { WORDCASE(SERVICE_CONTROL_STOP); WORDCASE(SERVICE_CONTROL_PAUSE); WORDCASE(SERVICE_CONTROL_CONTINUE); WORDCASE(SERVICE_CONTROL_INTERROGATE); WORDCASE(SERVICE_CONTROL_SHUTDOWN); WORDCASE(SERVICE_CONTROL_PARAMCHANGE); WORDCASE(SERVICE_CONTROL_NETBINDADD); WORDCASE(SERVICE_CONTROL_NETBINDREMOVE); WORDCASE(SERVICE_CONTROL_NETBINDENABLE); WORDCASE(SERVICE_CONTROL_NETBINDDISABLE); default: return _T("SERVICE_CONTROL_???"); } } /* * prterr() * * Given an error code, return a printable name of the ERROR_x * macro for it. Yes, the long strings are available via the * FormatMessage() function, but we've always preferred to do * it this way. * * Note that one can type "NET HELPMSG 1234" at a CMD prompt * to do this lookup on the command line. */ static const TCHAR *prterr(DWORD err) { static TCHAR errbuf[256]; switch (err) { WORDCASE(ERROR_SUCCESS); WORDCASE(ERROR_ACCESS_DENIED); WORDCASE(ERROR_SERVICE_EXISTS); WORDCASE(ERROR_SERVICE_DOES_NOT_EXIST); WORDCASE(ERROR_SERVICE_CANNOT_ACCEPT_CTRL); WORDCASE(ERROR_DEPENDENT_SERVICES_RUNNING); WORDCASE(ERROR_INVALID_SERVICE_CONTROL); WORDCASE(ERROR_SERVICE_REQUEST_TIMEOUT); WORDCASE(ERROR_SERVICE_NO_THREAD); WORDCASE(ERROR_SERVICE_DATABASE_LOCKED); WORDCASE(ERROR_SERVICE_ALREADY_RUNNING); WORDCASE(ERROR_INVALID_SERVICE_ACCOUNT); WORDCASE(ERROR_SERVICE_DISABLED); WORDCASE(ERROR_FAILED_SERVICE_CONTROLLER_CONNECT); WORDCASE(ERROR_EXCEPTION_IN_SERVICE); WORDCASE(ERROR_SERVICE_SPECIFIC_ERROR); WORDCASE(ERROR_INVALID_PARAMETER); WORDCASE(ERROR_INVALID_NAME); WORDCASE(ERROR_INVALID_HANDLE); WORDCASE(ERROR_DUPLICATE_SERVICE_NAME); WORDCASE(ERROR_CIRCULAR_DEPENDENCY); WORDCASE(ERROR_SERVICE_MARKED_FOR_DELETE); WORDCASE(ERROR_ALREADY_EXISTS); WORDCASE(ERROR_SHUTDOWN_IN_PROGRESS); WORDCASE(ERROR_DATABASE_DOES_NOT_EXIST); default: _stprintf(errbuf, _T("err#%ld"), err); return errbuf; } } static const TCHAR *prterr(void) { return prterr( GetLastError() ); }