/***************************************************************************/
/*                                                                         */
/* Project:     OpenSLP - OpenSource implementation of Service Location    */
/*              Protocol Version 2                                         */
/*                                                                         */
/* File:        slpd_win32.c                                               */
/*                                                                         */
/* Abstract:    Win32 specific part, to make SLPD run as a "service"       */
/*                                                                         */
/*-------------------------------------------------------------------------*/
/*                                                                         */
/*     Please submit patches to http://www.openslp.org                     */
/*                                                                         */
/*-------------------------------------------------------------------------*/
/*                                                                         */
/* Copyright (C) 2000 Caldera Systems, Inc                                 */
/* All rights reserved.                                                    */
/*                                                                         */
/* Redistribution and use in source and binary forms, with or without      */
/* modification, are permitted provided that the following conditions are  */
/* met:                                                                    */ 
/*                                                                         */
/*      Redistributions of source code must retain the above copyright     */
/*      notice, this list of conditions and the following disclaimer.      */
/*                                                                         */
/*      Redistributions in binary form must reproduce the above copyright  */
/*      notice, this list of conditions and the following disclaimer in    */
/*      the documentation and/or other materials provided with the         */
/*      distribution.                                                      */
/*                                                                         */
/*      Neither the name of Caldera Systems nor the names of its           */
/*      contributors may be used to endorse or promote products derived    */
/*      from this software without specific prior written permission.      */
/*                                                                         */
/* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS     */
/* `AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT      */
/* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR   */
/* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE CALDERA      */
/* SYSTEMS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, */
/* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT        */
/* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;  LOSS OF USE,  */
/* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON       */
/* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT */
/* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE   */
/* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.    */
/*                                                                         */
/***************************************************************************/


#include "slpd.h"

/*=========================================================================*/
/* slpd includes                                                           */
/*=========================================================================*/
#include "slpd_cmdline.h"
#include "slpd_log.h"
#include "slpd_property.h"
#include "slpd_database.h"
#include "slpd_socket.h"
#include "slpd_incoming.h"
#include "slpd_outgoing.h"
#include "slpd_knownda.h"


/*=========================================================================*/
/* common code includes                                                    */
/*=========================================================================*/
#include "slp_linkedlist.h"
#include "slp_xid.h"

SERVICE_STATUS          ssStatus;       /* current status of the service  */
SERVICE_STATUS_HANDLE   sshStatusHandle; 
BOOL                    bDebug = FALSE; 
TCHAR                   szErr[256];

/*-------------------------------------------------------------------------*/
extern int G_SIGTERM;
/* see slpd_main.c                                                         */
/*-------------------------------------------------------------------------*/


/*-------------------------------------------------------------------------*/
void LoadFdSets(SLPList* socklist, 
                int* highfd, 
                fd_set* readfds, 
                fd_set* writefds);
/* see slpd_main.c                                                         */
/*-------------------------------------------------------------------------*/


/*------------------------------------------------------------------------*/
void HandleSigTerm();
/* see slpd_main.c                                                        */
/*------------------------------------------------------------------------*/


/*------------------------------------------------------------------------*/
void HandleSigAlrm();
/* see slpd_main.c                                                        */
/*------------------------------------------------------------------------*/


/*--------------------------------------------------------------------------*/
BOOL ReportStatusToSCMgr(DWORD dwCurrentState, 
                         DWORD dwWin32ExitCode, 
                         DWORD dwWaitHint) 
/*                                                                          */
/*  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                                                       */
/*                                                                          */
/*--------------------------------------------------------------------------*/
{
    static DWORD dwCheckPoint = 1; 
    BOOL fResult = TRUE; 

    /* when debugging we don't report to the SCM    */
    if(G_SlpdCommandLine.action != SLPD_DEBUG)
    {
        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)))
        {
            SLPDLog("SetServiceStatus failed"); 
        }
    }
    return fResult; 
} 


/*--------------------------------------------------------------------------*/
LPTSTR GetLastErrorText( LPTSTR lpszBuf, DWORD dwSize ) 
/*                                                                          */
/*  PURPOSE: copies error message text to string                            */
/*                                                                          */
/*  PARAMETERS:                                                             */
/*    lpszBuf - destination buffer                                          */
/*    dwSize - size of buffer                                               */
/*                                                                          */
/*  RETURN VALUE:                                                           */
/*    destination buffer                                                    */
/*                                                                          */
/*--------------------------------------------------------------------------*/
{
    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');  
        sprintf( lpszBuf, "%s (0x%x)", lpszTemp, GetLastError() ); 
    } 

    if(lpszTemp)
    {
        LocalFree((HLOCAL) lpszTemp );
    }

    return lpszBuf; 
} 


/*--------------------------------------------------------------------------*/
void ServiceStop() 
/*--------------------------------------------------------------------------*/
{
    G_SIGTERM = 1;
    ReportStatusToSCMgr(SERVICE_STOPPED,       /* service state */
                        NO_ERROR,              /* exit code    */
                        3000);                 /* wait hint    */
} 


/*--------------------------------------------------------------------------*/
void ServiceStart (int argc, char **argv) 
/*--------------------------------------------------------------------------*/
{
    fd_set          readfds;
    fd_set          writefds;
    int             highfd;
    int             fdcount         = 0;
    time_t          curtime;
    time_t          alarmtime;
    struct timeval  timeout;
    WSADATA         wsaData; 
    WORD            wVersionRequested = MAKEWORD(1,1); 

    /*------------------------*/
    /* Service initialization */
    /*------------------------*/
    if(!ReportStatusToSCMgr(SERVICE_START_PENDING, /* service state*/
                            NO_ERROR,              /* exit code    */
                            3000))                 /* wait hint    */
    {
        goto cleanup;
    }

    if(WSAStartup(wVersionRequested, &wsaData) != 0)
    {
        (void)ReportStatusToSCMgr(SERVICE_STOP_PENDING, 
                                  NO_ERROR, 
                                  0); 
        goto cleanup;
    }

    /*------------------------*/
    /* Parse the command line */
    /*------------------------*/
    if(SLPDParseCommandLine(argc,argv))
    {
        ReportStatusToSCMgr(SERVICE_STOP_PENDING, /* service state    */
                            NO_ERROR,             /* exit code    */
                            0);                   /* wait hint    */
        goto cleanup_winsock;
    }
    if(!ReportStatusToSCMgr(SERVICE_START_PENDING, /* service state    */
                            NO_ERROR,              /* exit code    */
                            3000))                 /* wait hint    */
    {
        goto cleanup_winsock;
    }

    /*------------------------------*/
    /* Initialize the log file      */
    /*------------------------------*/
    if(SLPDLogFileOpen(G_SlpdCommandLine.logfile, 1))
    {
        SLPDLog("Could not open logfile %s\n",G_SlpdCommandLine.logfile);
        goto cleanup_winsock;
    }

    /*------------------------*/
    /* Seed the XID generator */
    /*------------------------*/
    SLPXidSeed();

    /*---------------------*/
    /* Log startup message */
    /*---------------------*/
    SLPDLog("****************************************\n");
    SLPDLogTime();
    SLPDLog("SLPD daemon started\n");
    SLPDLog("****************************************\n");
    SLPDLog("Command line = %s\n",argv[0]);
    SLPDLog("Using configuration file = %s\n",G_SlpdCommandLine.cfgfile);
    SLPDLog("Using registration file = %s\n",G_SlpdCommandLine.regfile);
    if(!ReportStatusToSCMgr(SERVICE_START_PENDING, /* service state    */
                            NO_ERROR,              /* exit code    */
                            3000))                 /* wait hint    */
    {
        goto cleanup_winsock;
    }


    /*--------------------------------------------------*/
    /* Initialize for the first time                    */
    /*--------------------------------------------------*/
    if(SLPDPropertyInit(G_SlpdCommandLine.cfgfile) ||
       SLPDDatabaseInit(G_SlpdCommandLine.regfile) ||
       SLPDIncomingInit() ||
       SLPDOutgoingInit() ||
       SLPDKnownDAInit())
    {
        SLPDLog("slpd initialization failed\n");
        goto cleanup_winsock;
    }
    SLPDLog("Agent Interfaces = %s\n",G_SlpdProperty.interfaces);
    SLPDLog("Agent URL = %s\n",G_SlpdProperty.myUrl);

    /* Service is now running, perform work until shutdown    */

    if(!ReportStatusToSCMgr(SERVICE_RUNNING,       /* service state    */
                            NO_ERROR,              /* exit code    */
                            0))                    /* wait hint    */
    {
        goto cleanup_winsock;
    }


    /*-----------*/
    /* Main loop */
    /*-----------*/
    SLPDLog("Startup complete entering main run loop ...\n\n");
    G_SIGTERM   = 0;
    curtime = time(&alarmtime);
    alarmtime = curtime + SLPD_AGE_INTERVAL;
    while(G_SIGTERM == 0)
    {
        /*--------------------------------------------------------*/
        /* Load the fdsets up with all valid sockets in the list  */
        /*--------------------------------------------------------*/
        highfd = 0;
        FD_ZERO(&readfds);
        FD_ZERO(&writefds);
        LoadFdSets(&G_IncomingSocketList, &highfd, &readfds,&writefds);
        LoadFdSets(&G_OutgoingSocketList, &highfd, &readfds,&writefds);

        /*--------------------------------------------------*/
        /* Before select(), check to see if we got a signal */
        /*--------------------------------------------------*/
        if(G_SIGALRM)
        {
            goto HANDLE_SIGNAL;
        }

        /*-------------*/
        /* Main select */
        /*-------------*/
        timeout.tv_sec = SLPD_AGE_INTERVAL;
        timeout.tv_usec = 0;
        fdcount = select(highfd+1,&readfds,&writefds,0,&timeout);
        if(fdcount > 0) /* fdcount will be < 0 when timed out */
        {
            SLPDIncomingHandler(&fdcount,&readfds,&writefds);
            SLPDOutgoingHandler(&fdcount,&readfds,&writefds);
        }

        /*----------------*/
        /* Handle signals */
        /*----------------*/
        HANDLE_SIGNAL:
        curtime = time(&curtime);
        if(curtime >= alarmtime)
        {
            HandleSigAlrm();
            alarmtime = curtime + SLPD_AGE_INTERVAL;
        }

    } /* End of main loop */

    /* Got SIGTERM */
    HandleSigTerm();

    cleanup_winsock:
    WSACleanup();

    cleanup: 
    ;
} 

/*==========================================================================*/
BOOL WINAPI ControlHandler ( DWORD dwCtrlType ) 
/*                                                                          */
/*  PURPOSE: Handled console control events                                 */
/*                                                                          */
/*  PARAMETERS:                                                             */
/*    dwCtrlType - type of control event                                    */
/*                                                                          */
/*  RETURN VALUE:                                                           */
/*    True - handled                                                        */
/*    False - unhandled                                                     */
/*                                                                          */
/*==========================================================================*/
{
    switch(dwCtrlType)
    {
    case CTRL_BREAK_EVENT:  /* use Ctrl+C or Ctrl+Break to simulate    */
    case CTRL_C_EVENT:      /* SERVICE_CONTROL_STOP in debug mode    */
        printf("Stopping %s.\n", G_SERVICEDISPLAYNAME); 
        ServiceStop(); 
        return TRUE; 
        break; 

    } 
    return FALSE; 
} 


/*==========================================================================*/
VOID WINAPI ServiceCtrl(DWORD dwCtrlCode) 
/*                                                                          */
/*  PURPOSE: This function is called by the SCM whenever                    */
/*           ControlService() is called on this service.                    */
/*                                                                          */
/*  PARAMETERS:                                                             */
/*    dwCtrlCode - type of control requested                                */
/*                                                                          */
/*  RETURN VALUE:                                                           */
/*    none                                                                  */
/*                                                                          */
/*==========================================================================*/
{
    /* Handle the requested control code.    */
    /*    */

    switch(dwCtrlCode)
    {
    /* Stop the service.    */
    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); 
} 


/*==========================================================================*/
void WINAPI SLPDServiceMain(DWORD argc, LPTSTR *argv) 
/*==========================================================================*/
{

    /* register our service control handler:    */
    sshStatusHandle = RegisterServiceCtrlHandler( G_SERVICENAME, ServiceCtrl); 

    if(sshStatusHandle != 0)
    {
        /* SERVICE_STATUS members that don't change    */
        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    */
        {
            ServiceStart(argc, argv); 
        }
    }


    /* try to report the stopped status to the service control manager.    */

    if(sshStatusHandle)
        (void)ReportStatusToSCMgr(SERVICE_STOPPED, 
                                  0, 
                                  0);


} 


/*--------------------------------------------------------------------------*/
void SLPDCmdInstallService(int automatic) 
/*--------------------------------------------------------------------------*/
{
    SC_HANDLE   schService; 
    SC_HANDLE   schSCManager; 

    DWORD start_type;
    TCHAR szPath[512]; 

    if(GetModuleFileName( NULL, szPath, 512 ) == 0)
    {
        printf("Unable to install %s - %s\n", 
               G_SERVICEDISPLAYNAME, 
               GetLastErrorText(szErr, 256)); 
        return; 
    }

    if(automatic)
    {
      start_type = SERVICE_AUTO_START;
    }
    else
    {
      start_type = SERVICE_DEMAND_START;
    } 

    schSCManager = OpenSCManager(
                                NULL,                   /* machine (NULL == local)    */
                                NULL,                   /* database (NULL == default)    */
                                SC_MANAGER_ALL_ACCESS); /* access required    */

    if(schSCManager)
    {
        schService = CreateService(
                                  schSCManager,               /* SCManager database    */
                                  G_SERVICENAME,        /* name of service    */
                                  G_SERVICEDISPLAYNAME, /* name to display    */
                                  SERVICE_ALL_ACCESS,         /* desired access    */
                                  SERVICE_WIN32_OWN_PROCESS,  /* service type    */
                                  start_type,       			  /* start type    */
                                  SERVICE_ERROR_NORMAL,       /* error control type    */
                                  szPath,                     /* service's binary    */
                                  NULL,                       /* no load ordering group    */
                                  NULL,                       /* no tag identifier    */
                                  "",       /* dependencies    */
                                  NULL,                       /* LocalSystem account    */
                                  NULL);                      /* no password    */

        if(schService)
        {
            printf("%s installed.\n", G_SERVICEDISPLAYNAME ); 
            CloseServiceHandle(schService); 
        }
        else
        {
            printf("CreateService failed - %s\n", GetLastErrorText(szErr, 256)); 
        } 

        CloseServiceHandle(schSCManager); 
    }
    else
        printf("OpenSCManager failed - %s\n", GetLastErrorText(szErr,256)); 
} 

static void SLPDHlpStopService(SC_HANDLE schService)
{
	/* try to stop the service    */
	if(ControlService(schService, SERVICE_CONTROL_STOP, &ssStatus))
	{
		printf("Stopping %s.", G_SERVICEDISPLAYNAME);
		Sleep(1000);
      
		while(QueryServiceStatus(schService, &ssStatus))
		{
			if(ssStatus.dwCurrentState == SERVICE_STOP_PENDING)
			{
				printf(".");
				Sleep(1000);
			}
			else
				break; 
		} 
      
		if(ssStatus.dwCurrentState == SERVICE_STOPPED)
			printf("\n%s stopped.\n", G_SERVICEDISPLAYNAME);
		else
			printf("\n%s failed to stop.\n", G_SERVICEDISPLAYNAME); 
	}
}
 
/*--------------------------------------------------------------------------*/
void SLPDCmdRemoveService() 
/*--------------------------------------------------------------------------*/
{
    SC_HANDLE schService; 
    SC_HANDLE schSCManager; 

    schSCManager = OpenSCManager(
                                NULL,                   /* machine (NULL == local)    */
                                NULL,                   /* database (NULL == default)    */
                                SC_MANAGER_ALL_ACCESS); /* access required    */
    if(schSCManager)
    {
        schService = OpenService(schSCManager, G_SERVICENAME, SERVICE_ALL_ACCESS); 

        if(schService)
        {
				SLPDHlpStopService(schService);

				/* now remove the service    */
            if(DeleteService(schService))
                printf("%s removed.\n", G_SERVICEDISPLAYNAME );
            else
                printf("DeleteService failed - %s\n", GetLastErrorText(szErr,256)); 


            CloseServiceHandle(schService); 
        }
        else
            printf("OpenService failed - %s\n", GetLastErrorText(szErr,256)); 

        CloseServiceHandle(schSCManager); 
    }
    else
        printf("OpenSCManager failed - %s\n", GetLastErrorText(szErr,256)); 
} 

/*--------------------------------------------------------------------------*/
void SLPDCmdStartService()
/*--------------------------------------------------------------------------*/
{
	 SC_HANDLE schService; 
	 SC_HANDLE schSCManager; 

	 schSCManager = OpenSCManager(
	 									  NULL,                   /* machine (NULL == local)    */
	 									  NULL,                   /* database (NULL == default) */
	 									  SC_MANAGER_ALL_ACCESS); /* access required    		  */
	 if(schSCManager)
	 {
	 	  schService = OpenService(schSCManager, G_SERVICENAME, SERVICE_ALL_ACCESS); 

	 	  if(schService)
	 	  {
	 			if( !StartService(schService, 0, NULL))
	 			{
	 				 printf("OpenService failed - %s\n", GetLastErrorText(szErr,256)); 
	 			}
	 			CloseServiceHandle(schService); 
	 	  }
	 	  else
	 	  {
	 	  		printf("OpenService failed - %s\n", GetLastErrorText(szErr,256)); 
	 	  }
	 	  CloseServiceHandle(schSCManager); 
	 }
	 else
	 {
	 	  printf("OpenSCManager failed - %s\n", GetLastErrorText(szErr,256)); 
	 }
}

/*--------------------------------------------------------------------------*/
void SLPDCmdStopService()
/*--------------------------------------------------------------------------*/
{
	 SC_HANDLE schService; 
	 SC_HANDLE schSCManager; 

	 schSCManager = OpenSCManager(
	 									  NULL,                   /* machine (NULL == local)    */
	 									  NULL,                   /* database (NULL == default) */
	 									  SC_MANAGER_ALL_ACCESS); /* access required    		  */
	 if(schSCManager)
	 {
	 	  schService = OpenService(schSCManager, G_SERVICENAME, SERVICE_ALL_ACCESS); 

	 	  if(schService)
	 	  {
	 			SLPDHlpStopService(schService);
	 			CloseServiceHandle(schService); 
	 	  }
	 	  else
	 	  {
	 			printf("OpenService failed - %s\n", GetLastErrorText(szErr,256)); 
	 	  }
	 	  CloseServiceHandle(schSCManager); 
	 }
	 else
	 {
	 	  printf("OpenSCManager failed - %s\n", GetLastErrorText(szErr,256)); 
	 }
}

/*--------------------------------------------------------------------------*/
void SLPDCmdDebugService(int argc, char ** argv) 
/*--------------------------------------------------------------------------*/
{
    printf("Debugging %s.\n", G_SERVICEDISPLAYNAME); 

    SetConsoleCtrlHandler( ControlHandler, TRUE ); 
    ServiceStart( argc, argv ); 
}

/*==========================================================================*/
void __cdecl main(int argc, char **argv) 
/*==========================================================================*/
{
    SERVICE_TABLE_ENTRY dispatchTable[] = 
    { 
        { G_SERVICENAME, (LPSERVICE_MAIN_FUNCTION)SLPDServiceMain}, 
        { NULL, NULL} 
    }; 

    /*------------------------*/
    /* Parse the command line */
    /*------------------------*/
    if(SLPDParseCommandLine(argc,argv))
    {
        SLPDFatal("Invalid command line\n");
    }

    switch(G_SlpdCommandLine.action)
    {
    case SLPD_DEBUG:
        SLPDCmdDebugService(argc, argv);
        break;
    case SLPD_INSTALL:
        SLPDCmdInstallService(G_SlpdCommandLine.autostart);
        break;
    case SLPD_REMOVE:
        SLPDCmdRemoveService();
        break;
    case SLPD_START:
        SLPDCmdStartService();
		  break;
    case SLPD_STOP:
        SLPDCmdStopService();
        break;
    default:
        SLPDPrintUsage();
        StartServiceCtrlDispatcher(dispatchTable);

        break;
    } 
} 




