// Lotus Notes Command Line Mail Client NOTES_MAIL by Gottfried Rudorfer // // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF // ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A // PARTICULAR PURPOSE. // // Copyright 2002 Gottfried Rudorfer. All Rights Reserved. // // MODULE: service.cpp // // PURPOSE: Implements service functions required by notes_mail // for windows. // // COMMENTS: // // AUTHOR: Gottfried Rudorfer // http://godefroy.sdf-eu.org/notes_mail/ // #ifndef _SERVICE_H #include "Notes_Mail.h" #endif // this event is signalled when the // service should end // HANDLE hServerStopEvent = NULL; // internal variables SERVICE_STATUS ssStatus; // current status of the service SERVICE_STATUS_HANDLE sshStatusHandle; DWORD dwErr = 0; BOOL bDebug = FALSE; TCHAR szErr[256]; int stop = 0; ////////////////////////////////////////////////////////////////////////////// //// todo: ServiceStart()must be defined by in your code. //// The service should use ReportStatusToSCMgr to indicate //// progress. This routine must also be used by StartService() //// to report to the SCM when the service is running. //// //// If a ServiceStop procedure is going to take longer than //// 3 seconds to execute, it should spawn a thread to //// execute the stop code, and return. Otherwise, the //// ServiceControlManager will believe that the service has //// stopped responding //// VOID ServiceStart(DWORD dwArgc, LPTSTR *lpszArgv) { HANDLE hEvents[1] = {NULL}; PSECURITY_DESCRIPTOR pSD = NULL; SECURITY_ATTRIBUTES sa; DWORD dwWait; Notes_Mail bMail; char strBuf[256]; unsigned int cSleep = 1000, cSleepDel = 10000; /////////////////////////////////////////////////// // // Service initialization // // report the status to the service control manager. // if (!ReportStatusToSCMgr( SERVICE_START_PENDING, // service state NO_ERROR, // exit code 3000)) // wait hint goto cleanup; // create the event object. The control handler function signals // this event when it receives the "stop" control code. // hServerStopEvent = CreateEvent( NULL, // no security attributes TRUE, // manual reset event FALSE, // not-signalled NULL); // no name if ( hServerStopEvent == NULL) goto cleanup; hEvents[0] = hServerStopEvent; // report the status to the service control manager. // if (!ReportStatusToSCMgr( SERVICE_START_PENDING, // service state NO_ERROR, // exit code 3000)) // wait hint goto cleanup; // create a security descriptor that allows anyone to write to // the pipe... // pSD = (PSECURITY_DESCRIPTOR) malloc( SECURITY_DESCRIPTOR_MIN_LENGTH ); if (pSD == NULL) goto cleanup; if (!InitializeSecurityDescriptor(pSD, SECURITY_DESCRIPTOR_REVISION)) goto cleanup; // add a NULL disc. ACL to the security descriptor. // if (!SetSecurityDescriptorDacl(pSD, TRUE, (PACL) NULL, FALSE)) goto cleanup; sa.nLength = sizeof(sa); sa.lpSecurityDescriptor = pSD; sa.bInheritHandle = TRUE; // report the status to the service control manager. // if (!ReportStatusToSCMgr( SERVICE_START_PENDING, // service state NO_ERROR, // exit code 3000)) // wait hint goto cleanup; bMail.GetInstPath(); bMail.CheckMailDirs(); // check Mail dirs // report the status to the service control manager. // if (!ReportStatusToSCMgr( SERVICE_RUNNING, // service state NO_ERROR, // exit code 0)) // wait hint goto cleanup; // // End of initialization // //////////////////////////////////////////////////////// //////////////////////////////////////////////////////// // // Service is now running, perform work until shutdown // bMail.logevt(("Service was started, starting main loop. version "+StringModuleVersion()).c_str(),EVENTLOG_INFORMATION_TYPE); while ( 1 ) // main loop { int retcode; retcode = bMail.OpenMailDB(); HANDLE dirH = NULL; WIN32_FIND_DATA fd; DWORD findRet; StringStringMap Files; string searchD = bMail.OutBox + "\\*"; dirH = FindFirstFile( searchD.c_str(), // pointer to name of file to search for &fd // pointer to returned information ); if (dirH != INVALID_HANDLE_VALUE) { if (strcmp(fd.cFileName, ".") && strcmp(fd.cFileName, "..") && \ (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))) Files[fd.cFileName] = "ok"; } else { string err="Service: Error processing directory "; err += bMail.OutBox.c_str(); err += " the error was: "; GetLastErrorText(strBuf, 255); err += strBuf; bMail.logevt(err.c_str(),EVENTLOG_ERROR_TYPE); } while( FindNextFile(dirH, &fd) ) { if (strcmp(fd.cFileName, ".") && strcmp(fd.cFileName, "..") && \ (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))) Files[fd.cFileName] = "ok"; } findRet = GetLastError(); if (ERROR_NO_MORE_FILES != findRet) { string err="Service: Error processing directory "; err += bMail.OutBox.c_str(); err += " the error was: "; GetLastErrorText(strBuf, 255); err += strBuf; bMail.logevt(err.c_str(),EVENTLOG_ERROR_TYPE); } // Send mails from outbox each loop for ( StringStringMap::iterator i = Files.begin(); i != Files.end(); ++i ) { string Name = i->first; int len = Name.length(); if ((len > PrefixLength) && (PrefixDelim == Name[-1+PrefixLength])) { bMail.SendMail(bMail.OutBox + "\\" + Name); } dwWait = WaitForMultipleObjects( 1, hEvents, TRUE, 100 ); // wait 0.1 seconds if ( dwWait == WAIT_OBJECT_0 ) { bMail.logevt("Quit on service stop request.",EVENTLOG_INFORMATION_TYPE); goto cleanup; break; // or server stop signaled } } // Process Inbox from Mail server if (CountSleepsIn <= cSleep) { bMail.ReceiveMail(); cSleep = 0; } // Delete old mails if (CountSleepsDel <= cSleepDel) { //bMail.logevt("Starting mailbox cleanup...",EVENTLOG_INFORMATION_TYPE); bMail.CleanDir("Received"); bMail.CleanDir("Sent"); //bMail.logevt("Finished mailbox cleanup.",EVENTLOG_INFORMATION_TYPE); cSleepDel = 0; } // Close mail connection each hour if (bMail.MailDBOpened) { struct _timeb timeNow; _ftime(&timeNow); if ((bMail.timeStrOpen.time + 60 * 60 * 1) < timeNow.time) { // close each hour bMail.CloseMailDB(); } } // Wait 30 seconds and then continue with loop dwWait = WaitForMultipleObjects( 1, hEvents, TRUE, ServiceSleepTime ); // wait 30 seconds if ( dwWait == WAIT_OBJECT_0 ) { bMail.logevt(("Quit on service stop request. version " + StringModuleVersion()).c_str(),EVENTLOG_INFORMATION_TYPE); break; // or server stop signaled } cSleep++; cSleepDel++; } cleanup: if (bMail.MailDBOpened) bMail.CloseMailDB(); if (hServerStopEvent) CloseHandle(hServerStopEvent); if ( pSD ) free( pSD ); } VOID ServiceStop() { ReportStatusToSCMgr( SERVICE_STOP_PENDING, // service state NO_ERROR, // exit code 10000); // wait hint if ( hServerStopEvent ) SetEvent(hServerStopEvent); } ////////////////////////////////////////////////////////////////////////////// // // FUNCTION: service_main // // PURPOSE: To perform actual initialization of the service // // PARAMETERS: // dwArgc - number of command line arguments // lpszArgv - array of command line arguments // // RETURN VALUE: // none // // COMMENTS: // This routine performs the service initialization and then calls // the user defined ServiceStart() routine to perform majority // of the work. // void WINAPI service_main(DWORD dwArgc, LPTSTR *lpszArgv) { // register our service control handler: // sshStatusHandle = RegisterServiceCtrlHandler( TEXT(SZSERVICENAME), service_ctrl); if (!sshStatusHandle) goto cleanup; // SERVICE_STATUS members that don't change in example // ssStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; ssStatus.dwServiceSpecificExitCode = 0; // report the status to the service control manager. // if (!ReportStatusToSCMgr( SERVICE_START_PENDING, // service state NO_ERROR, // exit code 3000)) // wait hint goto cleanup; ServiceStart( dwArgc, lpszArgv ); cleanup: // try to report the stopped status to the service control manager. // if (sshStatusHandle) (VOID)ReportStatusToSCMgr( SERVICE_STOPPED, dwErr, 0); return; } // // 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: // VOID WINAPI service_ctrl(DWORD dwCtrlCode) { // Handle the requested control code. // switch(dwCtrlCode) { // Stop the service. // // SERVICE_STOP_PENDING should be reported before // setting the Stop Event - hServerStopEvent - in // ServiceStop(). This avoids a race condition // which may result in a 1053 - The Service did not respond... // error. case SERVICE_CONTROL_STOP: ReportStatusToSCMgr(SERVICE_STOP_PENDING, NO_ERROR, 0); ServiceStop(); return; // Update the service status. // case SERVICE_CONTROL_INTERROGATE: break; // invalid control code // default: break; } ReportStatusToSCMgr(ssStatus.dwCurrentState, NO_ERROR, 0); } // // FUNCTION: ReportStatusToSCMgr() // // PURPOSE: Sets the current status of the service and // reports it to the Service Control Manager // // PARAMETERS: // dwCurrentState - the state of the service // dwWin32ExitCode - error code to report // dwWaitHint - worst case estimate to next checkpoint // // RETURN VALUE: // TRUE - success // FALSE - failure // // COMMENTS: // BOOL ReportStatusToSCMgr(DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwWaitHint) { static DWORD dwCheckPoint = 1; BOOL fResult = TRUE; if ( !bDebug ) // when debugging we don't report to the SCM { if (dwCurrentState == SERVICE_START_PENDING) ssStatus.dwControlsAccepted = 0; else ssStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP; ssStatus.dwCurrentState = dwCurrentState; ssStatus.dwWin32ExitCode = dwWin32ExitCode; ssStatus.dwWaitHint = dwWaitHint; if ( ( dwCurrentState == SERVICE_RUNNING ) || ( dwCurrentState == SERVICE_STOPPED ) ) ssStatus.dwCheckPoint = 0; else ssStatus.dwCheckPoint = dwCheckPoint++; // Report the status of the service to the service control manager. // if (!(fResult = SetServiceStatus( sshStatusHandle, &ssStatus))) { AddToMessageLog(TEXT("SetServiceStatus")); } } return fResult; } // // FUNCTION: AddToMessageLog(LPTSTR lpszMsg) // // PURPOSE: Allows any thread to log an error message // // PARAMETERS: // lpszMsg - text for message // // RETURN VALUE: // none // // COMMENTS: // VOID AddToMessageLog(LPTSTR lpszMsg) { TCHAR szMsg[256]; HANDLE hEventSource; LPTSTR lpszStrings[2]; if ( !bDebug ) { dwErr = GetLastError(); // Use event logging to log the error. // hEventSource = RegisterEventSource(NULL, TEXT(SZSERVICENAME)); _stprintf(szMsg, TEXT("%s error: %d"), TEXT(SZSERVICENAME), dwErr); lpszStrings[0] = szMsg; lpszStrings[1] = lpszMsg; 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 2, // strings in lpszStrings 0, // no bytes of raw data (const char **) lpszStrings, // array of error strings NULL); // no raw data (VOID) DeregisterEventSource(hEventSource); } } } /* Set the service description regardless of platform. * We revert to set_service_description_string on NT/9x. * This would be bad on Win2000, since it wouldn't * notify the service control manager of the name change. */ static void set_service_description_string(const char* service, const char *description) { char szPath[MAX_PATH]; HKEY hkey; /* Create/Find the Service key that Monitor Applications iterate */ sprintf(szPath, "SYSTEM\\CurrentControlSet\\Services\\%s", service); if (RegCreateKey(HKEY_LOCAL_MACHINE, szPath, &hkey) != ERROR_SUCCESS) { return; } /* Attempt to set the Description value for our service */ RegSetValueEx(hkey, "Description", 0, REG_SZ, (unsigned char *) description, strlen(description) + 1); RegCloseKey(hkey); } /* ChangeServiceConfig2() prototype: */ typedef WINADVAPI BOOL (WINAPI *CSD_T)(SC_HANDLE, DWORD, LPCVOID); /////////////////////////////////////////////////////////////////// // // The following code handles service installation and removal // // // FUNCTION: CmdInstallService() // // PURPOSE: Installs the service // // PARAMETERS: // none // // RETURN VALUE: // none // // COMMENTS: // int CmdInstallService() { SC_HANDLE schService; SC_HANDLE schSCManager; int retInt = 0; BOOL srvConfigRes; TCHAR szPath[512]; SERVICE_DESCRIPTION srvDesc; if ( GetModuleFileName( NULL, szPath, 500 ) == 0 ) { _tprintf(TEXT("Unable to install %s - %s\n"), TEXT(SZSERVICEDISPLAYNAME), GetLastErrorText(szErr, 256)); retInt += 1; return retInt; } LPSTR KeyName = "System\\CurrentControlSet\\Services\\EventLog\\Application\\Notes_Mail"; HKEY RnrKey; LONG err; DWORD Disposition; // // Add the data to the EventLog's registry key so that the // log insertion strings may be found by the Event Viewer. // err = RegCreateKeyEx( HKEY_LOCAL_MACHINE, KeyName, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &RnrKey, &Disposition ); if( err != 0 ) { printf( "RegCreateKeyEx failed: %ld\n", err ); exit(1); } err = RegSetValueEx( RnrKey, "EventMessageFile", 0, REG_EXPAND_SZ, (const unsigned char *) szPath, strlen( szPath ) + 1 ); if( err == 0 ) { DWORD Value; Value = EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE | EVENTLOG_INFORMATION_TYPE; err = RegSetValueEx( RnrKey, "TypesSupported", 0, REG_DWORD, (CONST BYTE *)&Value, sizeof(Value) ); } RegCloseKey( RnrKey ); if( err != 0 ) { printf( "RegSetValueEx failed: %ld\n", err ); exit(1); } strcat(szPath, " -service"); // pass main() argument -service to start as a service schSCManager = OpenSCManager( NULL, // machine (NULL == local) NULL, // database (NULL == default) SC_MANAGER_ALL_ACCESS // access required ); if ( schSCManager ) { schService = CreateService( schSCManager, // SCManager database TEXT(SZSERVICENAME), // name of service TEXT(SZSERVICEDISPLAYNAME), // name to display SERVICE_ALL_ACCESS, // desired access SERVICE_WIN32_OWN_PROCESS, // service type SERVICE_AUTO_START, // start type SERVICE_ERROR_NORMAL, // error control type szPath, // service's binary NULL, // no load ordering group NULL, // no tag identifier TEXT(SZDEPENDENCIES), // dependencies NULL, // LocalSystem account NULL); // no password if ( schService ) { HINSTANCE hwin2000scm; CSD_T ChangeServiceDescription; // Function pointer hwin2000scm = LoadLibrary("ADVAPI32.DLL"); if (!hwin2000scm) { set_service_description_string(TEXT(SZSERVICENAME), TEXT(SZSERVICEDISPLAYNAME)); } else { //The following function is on NT not available -> we perform function call ChangeServiceDescription = (CSD_T) GetProcAddress(hwin2000scm, "ChangeServiceConfig2A"); if (!ChangeServiceDescription) { FreeLibrary(hwin2000scm); set_service_description_string(TEXT(SZSERVICENAME), TEXT(SZSERVICEDISPLAYNAME)); } else { srvDesc.lpDescription = SZSERVICEDESCRIPTION; DWORD dwInfoLevel = SERVICE_CONFIG_DESCRIPTION; srvConfigRes = ChangeServiceDescription(schService, dwInfoLevel /* SERVICE_CONFIG_DESCRIPTION */, &srvDesc); //srvConfigRes = ChangeServiceConfig2(schService, SERVICE_CONFIG_DESCRIPTION, &srvDesc); if (!srvConfigRes) { _tprintf(TEXT("ChangeServiceConfig2 failed - %s\n"), GetLastErrorText(szErr, 256)); retInt += 8; } } } _tprintf(TEXT("%s installed.\n"), TEXT(SZSERVICEDISPLAYNAME) ); CloseServiceHandle(schService); } else { _tprintf(TEXT("CreateService failed - %s\n"), GetLastErrorText(szErr, 256)); retInt += 2; } CloseServiceHandle(schSCManager); } else { _tprintf(TEXT("OpenSCManager failed - %s\n"), GetLastErrorText(szErr,256)); retInt += 4; } return retInt; } // // FUNCTION: CmdRemoveService() // // PURPOSE: Stops and removes the service // // PARAMETERS: // none // // RETURN VALUE: // none // // COMMENTS: // int CmdRemoveService() { SC_HANDLE schService; SC_HANDLE schSCManager; int retInt = 0; schSCManager = OpenSCManager( NULL, // machine (NULL == local) NULL, // database (NULL == default) SC_MANAGER_ALL_ACCESS // access required ); if ( schSCManager ) { schService = OpenService(schSCManager, TEXT(SZSERVICENAME), SERVICE_ALL_ACCESS); if (schService) { // try to stop the service if ( ControlService( schService, SERVICE_CONTROL_STOP, &ssStatus ) ) { _tprintf(TEXT("Stopping %s."), TEXT(SZSERVICEDISPLAYNAME)); Sleep( 1000 ); while( QueryServiceStatus( schService, &ssStatus ) ) { if ( ssStatus.dwCurrentState == SERVICE_STOP_PENDING ) { _tprintf(TEXT(".")); Sleep( 1000 ); } else break; } if ( ssStatus.dwCurrentState == SERVICE_STOPPED ) _tprintf(TEXT("\n%s stopped.\n"), TEXT(SZSERVICEDISPLAYNAME) ); else _tprintf(TEXT("\n%s failed to stop.\n"), TEXT(SZSERVICEDISPLAYNAME) ); } // now remove the service if( DeleteService(schService) ) _tprintf(TEXT("%s removed.\n"), TEXT(SZSERVICEDISPLAYNAME) ); else { _tprintf(TEXT("DeleteService failed - %s\n"), GetLastErrorText(szErr,256)); retInt += 1; } CloseServiceHandle(schService); } else { _tprintf(TEXT("OpenService failed - %s\n"), GetLastErrorText(szErr,256)); retInt += 2; } CloseServiceHandle(schSCManager); } else { _tprintf(TEXT("OpenSCManager failed - %s\n"), GetLastErrorText(szErr,256)); retInt += 4; } return retInt; } /////////////////////////////////////////////////////////////////// // // The following code is for running the service as a console app // // // FUNCTION: CmdDebugService(int argc, char ** argv) // // PURPOSE: Runs the service as a console application // // PARAMETERS: // argc - number of command line arguments // argv - array of command line arguments // // RETURN VALUE: // none // // COMMENTS: // void CmdDebugService(int argc, char ** argv) { DWORD dwArgc; LPTSTR *lpszArgv; #ifdef UNICODE lpszArgv = CommandLineToArgvW(GetCommandLineW(), &(dwArgc) ); #else dwArgc = (DWORD) argc; lpszArgv = argv; #endif _tprintf(TEXT("Debugging %s.\n"), TEXT(SZSERVICEDISPLAYNAME)); SetConsoleCtrlHandler( ControlHandler, TRUE ); ServiceStart( dwArgc, lpszArgv ); } // // FUNCTION: ControlHandler ( DWORD dwCtrlType ) // // PURPOSE: Handled console control events // // PARAMETERS: // dwCtrlType - type of control event // // RETURN VALUE: // True - handled // False - unhandled // // COMMENTS: // BOOL WINAPI ControlHandler ( DWORD dwCtrlType ) { switch( dwCtrlType ) { case CTRL_BREAK_EVENT: // use Ctrl+C or Ctrl+Break to simulate case CTRL_C_EVENT: // SERVICE_CONTROL_STOP in debug mode _tprintf(TEXT("Stopping %s.\n"), TEXT(SZSERVICEDISPLAYNAME)); ServiceStop(); return TRUE; break; } return FALSE; } // // FUNCTION: GetLastErrorText // // PURPOSE: copies error message text to string // // PARAMETERS: // lpszBuf - destination buffer // dwSize - size of buffer // // RETURN VALUE: // destination buffer // // COMMENTS: // LPTSTR GetLastErrorText( LPTSTR lpszBuf, DWORD dwSize ) { DWORD dwRet; LPTSTR lpszTemp = NULL; dwRet = FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |FORMAT_MESSAGE_ARGUMENT_ARRAY, NULL, GetLastError(), LANG_NEUTRAL, (LPTSTR)&lpszTemp, 0, NULL ); // supplied buffer is not long enough if ( !dwRet || ( (long)dwSize < (long)dwRet+14 ) ) lpszBuf[0] = TEXT('\0'); else { lpszTemp[lstrlen(lpszTemp)-2] = TEXT('\0'); //remove cr and newline character _stprintf( lpszBuf, TEXT("%s (0x%x)"), lpszTemp, GetLastError() ); } if ( lpszTemp ) LocalFree((HLOCAL) lpszTemp ); return lpszBuf; }