// Service.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include "ServiceMsg.h"
#include <malloc.h>

#define ServiceName "CVS"
#define DisplayName "CVS NT Service"
#define VersionString "1.10.7NT"
#define CVS_PORT 2401

static void CALLBACK ServiceMain(DWORD dwArgc, LPTSTR *lpszArgv);
static void CALLBACK ServiceHandler(DWORD fdwControl);
static BOOL NotifySCM(DWORD dwState, DWORD dwWin32ExitCode, DWORD dwProgress);
static char* basename(const char* str);
static LPCTSTR GetErrorString();
static void AddEventSource(LPCTSTR szService, LPCTSTR szModule);
static void ReportError(LPCTSTR szError, BOOL bError = TRUE);
static DWORD CALLBACK DoCvsThread(LPVOID lpParam);
static DWORD CALLBACK DoCvsPipeThread(LPVOID lpParam);
static DWORD CALLBACK DoPipeThread(LPVOID lpParam);

static DWORD   g_dwCurrentState;
static SERVICE_STATUS_HANDLE  g_hService;
static SOCKET g_hSocket;
static BOOL g_bStop = FALSE;
static BOOL g_bTestMode = FALSE;

int main(int argc, char* argv[])
{
    SC_HANDLE  hSCManager = NULL, hService = NULL;
    char szImagePath[MAX_PATH];
	char szTmp[MAX_PATH];
	HKEY hk;
	DWORD dwType,dwTmp;
	SERVICE_TABLE_ENTRY ServiceTable[] =
	{
		{ ServiceName, ServiceMain },
		{ NULL, NULL }
	};

	if(argc==1)
	{
		// Attempt to start service.  If this fails we're probably
		// not running as a service
		if(!StartServiceCtrlDispatcher(ServiceTable)) return 0;
	}
	if(argc!=2 || (strcmp(argv[1],"-i") && strcmp(argv[1],"-u") && strcmp(argv[1],"-test")))
	{
		fprintf(stderr,"NT CVS Service Handler\n\nArguments:\n\t%s -i\tInstall\n\t%s -u\tUninstall\n\t%s -test\tInteractive run",basename(argv[0]),basename(argv[0]),basename(argv[0]));
		return -1;
	}

	if(RegCreateKeyEx(HKEY_LOCAL_MACHINE,"Software\\CVS\\Pserver",NULL,"",REG_OPTION_NON_VOLATILE,KEY_ALL_ACCESS,NULL,&hk,NULL))
	{ 
		fprintf(stderr,"Couldn't create HKLM\\Software\\CVS\\Pserver key, error %d\n",GetLastError());
		return -1;
	}
	dwTmp=sizeof(szTmp);
	if(RegQueryValueEx(hk,"",NULL,&dwType,(BYTE*)szTmp,&dwTmp) || (GetFileAttributes(szTmp)==(DWORD)-1))
	{ 
		fprintf(stderr,"Invalid repository root '%s' in HKLM\\Software\\CVS\\Pserver\n",szTmp);
		return -1;
	}

	RegCloseKey(hk);

	if(!strcmp(argv[1],"-i"))
	{
		// connect to  the service control manager
		if((hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE)) == NULL)
		{
			fprintf(stderr,"OpenSCManager Failed\n");
			return -1;
		}

		GetModuleFileName(NULL,szImagePath,MAX_PATH);
		if ((hService = CreateService(hSCManager,ServiceName,DisplayName,
						STANDARD_RIGHTS_REQUIRED, SERVICE_WIN32_OWN_PROCESS,
						SERVICE_AUTO_START, SERVICE_ERROR_IGNORE,
						szImagePath, NULL, NULL, NULL, NULL, NULL)) == NULL)
		{
			fprintf(stderr,"CreateService Failed: %s\n",GetErrorString());
			return -1;
		}
		CloseServiceHandle(hService);
		CloseServiceHandle(hSCManager);
		ReportError(DisplayName" version "VersionString" installed successfully",FALSE);
		printf(DisplayName" Installed successfully\n");
	}
	else if(!strcmp(argv[1],"-u"))
	{
		// connect to  the service control manager
		if((hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE)) == NULL)
		{
			fprintf(stderr,"OpenSCManager Failed\n");
			return -1;
		}

		if((hService=OpenService(hSCManager,ServiceName,DELETE))==NULL)
		{
			fprintf(stderr,"OpenService Failed: %s\n",GetErrorString());
			return -1;
		}
		if(!DeleteService(hService))
		{
			fprintf(stderr,"DeleteService Failed: %s\n",GetErrorString());
			return -1;
		}
		CloseServiceHandle(hService);
		CloseServiceHandle(hSCManager);
		ReportError(ServiceName" uninstalled successfully",FALSE);
		printf(DisplayName" Uninstalled successfully\n");
	}	
	else if(!strcmp(argv[1],"-test"))
	{
		ServiceMain(999,NULL);
	}
	return 0;
}

void CALLBACK ServiceMain(DWORD dwArgc, LPTSTR *lpszArgv)
{
	char szTmp[10240];
	char szTmp2[10240];
	DWORD dwTmp,dwType;
	HKEY hk;
	int seq=1;

	if(dwArgc!=999)
	{
		if (!(g_hService = RegisterServiceCtrlHandler(ServiceName,ServiceHandler))) { ReportError("Unable to start "ServiceName" - RegisterServiceCtrlHandler failed"); return; }
		NotifySCM(SERVICE_START_PENDING, 0, seq++);
	}
	else
		g_bTestMode=TRUE;

	if(RegOpenKeyEx(HKEY_LOCAL_MACHINE,"SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment",NULL,KEY_QUERY_VALUE,&hk))
		{ ReportError("Unable to start "ServiceName" - Couldn't open environment key"); return; }

	dwTmp=sizeof(szTmp);
	if(RegQueryValueEx(hk,"PATH",NULL,&dwType,(BYTE*)szTmp,&dwTmp))
		{ ReportError("Unable to start "ServiceName" - PATH environment variable not defined in system environment"); return; }
	ExpandEnvironmentStrings(szTmp,szTmp2,sizeof(szTmp));
	SetEnvironmentVariable("PATH",szTmp2);

	RegCloseKey(hk);

	if(RegOpenKeyEx(HKEY_LOCAL_MACHINE,"Software\\CVS\\Pserver",NULL,KEY_QUERY_VALUE,&hk))
		{ ReportError("Unable to start "ServiceName" - Couldn't open HKLM\\Software\\CVS\\Pserver key"); return; }
	dwTmp=sizeof(szTmp);
	if(RegQueryValueEx(hk,"",NULL,&dwType,(BYTE*)szTmp,&dwTmp))
		{ ReportError("Unable to start "ServiceName" - Pserver root not defined in HKLM\\Software\\CVS\\Pserver"); return; }
	ExpandEnvironmentStrings(szTmp,szTmp2,sizeof(szTmp));
	SetEnvironmentVariable("ALLOWROOT",szTmp2);

	RegCloseKey(hk);

	if(!g_bTestMode)
		NotifySCM(SERVICE_START_PENDING, 0, seq++);

// Initialisation
    WSADATA data;

    if(WSAStartup (MAKEWORD (1, 1), &data))
	{
		ReportError("WSAStartup failed",TRUE);
		if(!g_bTestMode)
			NotifySCM(SERVICE_STOPPED,0,1);
		else
			printf("WSAStartup failed... aborting - Error %d\n",WSAGetLastError());
		return;
	}

   // @@jmerl: support for services
	sockaddr_in sin;

   // Get port from known services
   struct servent *sptr = getservbyname("cvspserver", "tcp");
   u_short usPort;

   if (sptr) {
      // Get specific cvpserver port number
      usPort = sptr->s_port;
   }
   else {
      // Get default cvspserver port number
      usPort = htons(CVS_PORT);
   }

 	g_hSocket=WSASocket(AF_INET,SOCK_STREAM,IPPROTO_TCP,NULL,0,0);
	sin.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
	sin.sin_port=usPort;
	sin.sin_family=AF_INET;
	if(bind(g_hSocket,(sockaddr*)&sin,sizeof(sockaddr_in)))
	{
		ReportError("Bind to port failed",TRUE);
		if(!g_bTestMode)
			NotifySCM(SERVICE_STOPPED,0,1);
		else
			printf("Bind to port failed... aborting - Error %d\n",WSAGetLastError());
		return;
	}

	if(listen(g_hSocket,50)==SOCKET_ERROR)
	{
		ReportError("Listen on socket failed",TRUE);
		if(!g_bTestMode)
			NotifySCM(SERVICE_STOPPED,0,1);
		else
			printf("Listen failed... aborting - Error %d\n",WSAGetLastError());
		return;
	}

	// Process running, wait for closedown
	ReportError(ServiceName" started successfully",FALSE);
	if(!g_bTestMode)
		NotifySCM(SERVICE_RUNNING, 0, 0);

	g_bStop=FALSE;

	CreateThread(NULL,0,DoPipeThread,NULL,0,NULL);

	do
	{
		fd_set rfd;

		FD_ZERO(&rfd);
		FD_SET(g_hSocket,&rfd);
		TIMEVAL tv = { 5, 0 }; // 5 seconds max wait
		int sel=select(1,&rfd,NULL,NULL,&tv);
		if(g_bStop || sel==SOCKET_ERROR) break; // Error on socket, or stopped
		if(!FD_ISSET(g_hSocket,&rfd)) continue; // Timeout
		SOCKET hConn=accept(g_hSocket,(struct sockaddr*)&sin,NULL);
		CreateThread(NULL,0,DoCvsThread,(void*)hConn,0,NULL);
	} while(1);

	NotifySCM(SERVICE_STOPPED, 0, 0);
	ReportError(ServiceName" stopped successfully",FALSE);
}

void CALLBACK ServiceHandler(DWORD fdwControl)
{
	switch(fdwControl)
	{      
	case SERVICE_CONTROL_STOP:
		OutputDebugString(ServiceName": Stop\n");
		NotifySCM(SERVICE_STOP_PENDING, 0, 1);
		g_bStop=TRUE;
		break;
    case SERVICE_CONTROL_INTERROGATE:
		OutputDebugString(ServiceName": Interrogate\n");
		NotifySCM(g_dwCurrentState, 0, 0);
		break;
   }
}

BOOL NotifySCM(DWORD dwState, DWORD dwWin32ExitCode, DWORD dwProgress)
{
	SERVICE_STATUS ServiceStatus;

	// fill in the SERVICE_STATUS structure
	ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
	ServiceStatus.dwCurrentState = g_dwCurrentState = dwState;
	ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
	ServiceStatus.dwWin32ExitCode = dwWin32ExitCode;
	ServiceStatus.dwServiceSpecificExitCode = 0;
	ServiceStatus.dwCheckPoint = dwProgress;
	ServiceStatus.dwWaitHint = 3000;

	// send status to SCM
	return SetServiceStatus(g_hService, &ServiceStatus);
}

char* basename(const char* str)
{
	char*p = ((char*)str)+strlen(str)-1;
	while(p>str && *p!='\\')
		p--;
	if(p>str) return (p+1);
	else return p;
}

LPCTSTR GetErrorString()
{
	static char ErrBuf[1024];

	FormatMessage(
    FORMAT_MESSAGE_FROM_SYSTEM |
	FORMAT_MESSAGE_IGNORE_INSERTS,
    NULL,
    GetLastError(),
    MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
    (LPTSTR) ErrBuf,
    sizeof(ErrBuf),
    NULL );
	return ErrBuf;
};

void ReportError(LPCTSTR szError, BOOL bError)
{
	static BOOL bEventSourceAdded = FALSE;

	if(!bEventSourceAdded)
	{
		char szModule[MAX_PATH];
		GetModuleFileName(NULL,szModule,MAX_PATH);
		AddEventSource(ServiceName,szModule);
		bEventSourceAdded=TRUE;
	}

	HANDLE hEvent = RegisterEventSource(NULL,  ServiceName);
	ReportEvent(hEvent,bError?EVENTLOG_ERROR_TYPE:EVENTLOG_INFORMATION_TYPE,0,MSG_STRING,NULL,1,0,&szError,NULL);
	DeregisterEventSource(hEvent);
}

void AddEventSource(LPCTSTR szService, LPCTSTR szModule)
{
	HKEY hk;
	DWORD dwData;
	char szKey[1024];

	strcpy(szKey,"SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application\\");
	strcat(szKey,szService);

    // Add your source name as a subkey under the Application 
    // key in the EventLog registry key.  
    if (RegCreateKey(HKEY_LOCAL_MACHINE, szKey, &hk))
		return; // Fatal error, no key and no way of reporting the error!!!

    // Add the name to the EventMessageFile subkey.  
    if (RegSetValueEx(hk,             // subkey handle 
            "EventMessageFile",       // value name 
            0,                        // must be zero 
            REG_EXPAND_SZ,            // value type 
            (LPBYTE) szModule,           // pointer to value data 
            strlen(szModule) + 1))       // length of value data 
			return; // Couldn't set key

    // Set the supported event types in the TypesSupported subkey.  
    dwData = EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE | 
        EVENTLOG_INFORMATION_TYPE;  
    if (RegSetValueEx(hk,      // subkey handle 
            "TypesSupported",  // value name 
            0,                 // must be zero 
            REG_DWORD,         // value type 
            (LPBYTE) &dwData,  // pointer to value data 
            sizeof(DWORD)))    // length of value data 
			return; // Couldn't set key
	RegCloseKey(hk); 
} 

DWORD CALLBACK DoCvsThread(LPVOID lpParam)
{
	SOCKET hSocket=(SOCKET)lpParam;
	char szTmp[10240],szTmp2[10240];
	strcpy(szTmp2,"cvs --allow-root=%ALLOWROOT% pserver");
	ExpandEnvironmentStrings(szTmp2,szTmp,sizeof(szTmp));

	STARTUPINFO si= { sizeof(STARTUPINFO) };
	PROCESS_INFORMATION pi = { 0 };
	HANDLE hReadPipeClient,hWritePipeClient,hErrorPipeClient;
	SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES), NULL, TRUE };

	DuplicateHandle(GetCurrentProcess(),(HANDLE)hSocket,GetCurrentProcess(),&hErrorPipeClient,0,TRUE,DUPLICATE_SAME_ACCESS);
	DuplicateHandle(GetCurrentProcess(),(HANDLE)hSocket,GetCurrentProcess(),&hReadPipeClient,0,TRUE,DUPLICATE_SAME_ACCESS);
	DuplicateHandle(GetCurrentProcess(),(HANDLE)hSocket,GetCurrentProcess(),&hWritePipeClient,0,TRUE,DUPLICATE_SAME_ACCESS);

	si.dwFlags=STARTF_USESTDHANDLES|STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;
	si.hStdInput=hReadPipeClient;
	si.hStdOutput=hWritePipeClient;
	si.hStdError=hErrorPipeClient;
	si.wShowWindow=SW_HIDE;

	if(!CreateProcess(NULL,(char*)(LPCTSTR)szTmp,NULL,NULL,TRUE,0,NULL,NULL,&si,&pi))
	{
		ReportError("Couldn't start cvs.exe",TRUE);
		return -1;
	}

	CloseHandle(hReadPipeClient);
	CloseHandle(hWritePipeClient);
	CloseHandle(hErrorPipeClient);

	while(!g_bStop && (WaitForSingleObject(pi.hProcess,200)==WAIT_TIMEOUT))
		;
	if(g_bStop)
		TerminateProcess(pi.hProcess,-1);

	CloseHandle(pi.hProcess);
	CloseHandle(pi.hThread);
	closesocket(hSocket);

	return 0;
}

DWORD CALLBACK DoCvsPipeThread(LPVOID lpParam)
{
	HANDLE hPipe=(HANDLE)lpParam;
	char szTmp[10240],szTmp2[10240];
	strcpy(szTmp2,"cvs --allow-root=%ALLOWROOT% ntserver");
	ExpandEnvironmentStrings(szTmp2,szTmp,sizeof(szTmp));

	STARTUPINFO si= { sizeof(STARTUPINFO) };
	PROCESS_INFORMATION pi = { 0 };
	HANDLE hReadPipeClient,hWritePipeClient,hErrorPipeClient;
	SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES), NULL, TRUE };

	DuplicateHandle(GetCurrentProcess(),(HANDLE)hPipe,GetCurrentProcess(),&hErrorPipeClient,0,TRUE,DUPLICATE_SAME_ACCESS);
	DuplicateHandle(GetCurrentProcess(),(HANDLE)hPipe,GetCurrentProcess(),&hReadPipeClient,0,TRUE,DUPLICATE_SAME_ACCESS);
	DuplicateHandle(GetCurrentProcess(),(HANDLE)hPipe,GetCurrentProcess(),&hWritePipeClient,0,TRUE,DUPLICATE_SAME_ACCESS);

	si.dwFlags=STARTF_USESTDHANDLES|STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;
	si.hStdInput=hReadPipeClient;
	si.hStdOutput=hWritePipeClient;
	si.hStdError=hErrorPipeClient;
	si.wShowWindow=SW_HIDE;

	if(!CreateProcess(NULL,(char*)(LPCTSTR)szTmp,NULL,NULL,TRUE,0,NULL,NULL,&si,&pi))
	{
		ReportError("Couldn't start cvs.exe",TRUE);
		return -1;
	}

	CloseHandle(hReadPipeClient);
	CloseHandle(hWritePipeClient);
	CloseHandle(hErrorPipeClient);

	while(!g_bStop && (WaitForSingleObject(pi.hProcess,200)==WAIT_TIMEOUT))
		;
	if(g_bStop)
		TerminateProcess(pi.hProcess,-1);

	CloseHandle(pi.hProcess);
	CloseHandle(pi.hThread);
	DisconnectNamedPipe(hPipe);
	CloseHandle(hPipe);

	return 0;
}

DWORD CALLBACK DoPipeThread(LPVOID lpParam)
{
	SECURITY_ATTRIBUTES sa;
	PSECURITY_DESCRIPTOR pSD;

    // create a security descriptor that allows anyone to write to 
    //  the pipe...
	// 
    pSD = (PSECURITY_DESCRIPTOR) malloc( SECURITY_DESCRIPTOR_MIN_LENGTH );  
    if (pSD == NULL)
		return FALSE;
    if (!InitializeSecurityDescriptor(pSD, SECURITY_DESCRIPTOR_REVISION)) 
		return FALSE;
	// add a NULL disc. ACL to the security descriptor. 
    //
	if (!SetSecurityDescriptorDacl(pSD, TRUE, (PACL) NULL, FALSE)) 
		return FALSE;

	sa.nLength = sizeof(sa); 
    sa.lpSecurityDescriptor = pSD;
	sa.bInheritHandle = TRUE; 
	
	HANDLE hPipe = CreateNamedPipe("\\\\.\\pipe\\CVS_PIPE",PIPE_ACCESS_DUPLEX|FILE_FLAG_WRITE_THROUGH,PIPE_WAIT,PIPE_UNLIMITED_INSTANCES,BUFSIZ,BUFSIZ,5000,&sa);
	if(hPipe==INVALID_HANDLE_VALUE)
	{
		if(g_bTestMode)
			printf("Couldn't create named pipe - error %d\n",GetLastError());
		ReportError("Couldn't create cvs named pipe",TRUE);
		return -1;
	}
	while(!g_bStop)
	{
		ConnectNamedPipe(hPipe,NULL);
		if(GetLastError()==ERROR_PIPE_CONNECTED)
		{
			CreateThread(NULL,0,DoCvsPipeThread,(void*)hPipe,0,NULL);
			hPipe = CreateNamedPipe("\\\\.\\pipe\\CVS_PIPE",PIPE_ACCESS_DUPLEX|FILE_FLAG_WRITE_THROUGH,PIPE_WAIT,PIPE_UNLIMITED_INSTANCES,BUFSIZ,BUFSIZ,5000,&sa);
			if(hPipe==INVALID_HANDLE_VALUE)
			{
				if(g_bTestMode)
					printf("Couldn't create named pipe - error %d\n",GetLastError());
				ReportError("Couldn't create cvs named pipe",TRUE);
				return -1;
			}
		}
		Sleep(1000);
	}
	return 0;
}
