/*
 * Based on the article at http://www.devx.com/cplus/Article/9857
 * and the WinNT service example from Makc <makc.the.great@gmail.com>
 * Adopted for SHTTPD by Christos Tsirimokos, xt@thepcguyz.com
 *
 * This is fully functional and not just an example.
 * Substitute  standalone.c  with  nt_service.c  in the  Makefile  and
 * after building you will have an  shttpd.exe  that works as a service.
 *
 * Use  shttp.exe -i  to install the service,
 *      shttp.exe -u  to uninstall it.
 * Use  net start "SHTTPD web server"  to start it,
 *      net stop "SHTTPD web server"   to stop it.
 * (you must have administrator rights for the above to work)
 *
 * If you do not have an  shttpd.conf  file, it will serve documents from
 * and create log files in the same folder where the executable is located.
 */

#include <windows.h>
#include <stdio.h>

#include "shttpd.h"
#include "defs.h"

/* service name, 64 chars max */
#define SHTTPD_NT_SERVICE_NAME "SHTTPD web server"

SERVICE_STATUS ServiceStatus; 
SERVICE_STATUS_HANDLE hStatus; 

BOOL ServiceIsInstalled();
BOOL ServiceInstall();
BOOL ServiceUninstall();
BOOL ServiceStart(); 
void ServiceMain(int argc, char** argv); 
void ControlHandler(DWORD request); 


int main(int argc, char *argv[]) 
{ 
    int iCode;
    BOOL bRun, bInstalled;

    iCode = 0;
    bRun = FALSE;
    bInstalled = ServiceIsInstalled();

    switch (argc)
    {
        /* no arguments */
        case 1:
           if (bInstalled)
               bRun = TRUE;
           else
               printf("%s, use -i to install service, -u to uninstall.\n", SHTTPD_NT_SERVICE_NAME);
           break;
        
        /* install/uninstall */   
        case 2:
            if (_stricmp(argv[1], "-u") == 0) {
                if(bInstalled)
                    if (ServiceUninstall())
                        printf("Service \"%s\" was successfully uninstalled.\n", SHTTPD_NT_SERVICE_NAME);
                    else {
                        iCode = 1;
                        printf("Failed to uninstall service \"%s\".\n", SHTTPD_NT_SERVICE_NAME);
                    }
                else
                    printf("Nothing to uninstall, this service in not installed.\n");
            }
            else if (_stricmp(argv[1], "-i") == 0) {
                if(bInstalled)
                    printf("This service is already installed as \"%s\".\n", SHTTPD_NT_SERVICE_NAME);
                else {
                    if (ServiceInstall()) {
                        bRun = TRUE;
                        printf("This service was successfully installed as \"%s\".\n", SHTTPD_NT_SERVICE_NAME);
                    }
                    else {
                        iCode = 1;
                        printf("Failed to install service \"%s\".\n", SHTTPD_NT_SERVICE_NAME);
                    }               
                }
            }
            else
                printf("%s, use -i to install service, -u to uninstall.\n", SHTTPD_NT_SERVICE_NAME);
            break;        

        /* help is needed */
        default:
            printf("%s, use -i to install service, -u to uninstall.\n", SHTTPD_NT_SERVICE_NAME);
            break;
    }

    if (bRun) {
       if (ServiceStart())
           printf("Service \"%s\" was successfully started.\n", SHTTPD_NT_SERVICE_NAME);
       else {
           iCode = 1;
           if (GetLastError() == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT)
               printf("Service installed; to start it, type this command: net start \"%s\"\n", SHTTPD_NT_SERVICE_NAME);
           else
               printf("Failed to start service \"%s\".\n", SHTTPD_NT_SERVICE_NAME);
       }
    }

    return iCode;
}


BOOL ServiceIsInstalled()
{
    BOOL bResult;
    SC_HANDLE hSCM, hService;
    
    hSCM = OpenSCManager(NULL, /* local machine */
                         NULL, /* ServicesActive database */
                         SC_MANAGER_ALL_ACCESS); /* full access */

    bResult = FALSE;

    if (hSCM) {
        hService = OpenService(hSCM,
                               SHTTPD_NT_SERVICE_NAME,
                               SERVICE_QUERY_CONFIG);

        if (hService) {
            bResult = TRUE;
            CloseServiceHandle(hService);
        }
        CloseServiceHandle(hSCM);
    }

    return bResult;
}


BOOL ServiceInstall()
{
    SC_HANDLE hSCM, hService;
    char szFilePath[FILENAME_MAX], szKey[256];
    HKEY hKey;
    DWORD dwData;
    
    hSCM = OpenSCManager(NULL, /* local machine */
                         NULL, /* ServicesActive database */
                         SC_MANAGER_ALL_ACCESS); /* full access */

    if (!hSCM) return FALSE;

    GetModuleFileName(NULL, szFilePath, sizeof(szFilePath));

    hService = CreateService(hSCM,
                             SHTTPD_NT_SERVICE_NAME,
                             SHTTPD_NT_SERVICE_NAME,
                             SERVICE_ALL_ACCESS,
                             SERVICE_WIN32_OWN_PROCESS,
                             SERVICE_AUTO_START, /* start condition */
                             SERVICE_ERROR_NORMAL,
                             szFilePath,
                             NULL,
                             NULL,
                             NULL,
                             NULL,
                             NULL);

    if (!hService) {
        CloseServiceHandle(hSCM);
        return FALSE;
    }

    hKey = NULL;
    strcpy(szKey, "SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application\\");
    strcat(szKey, SHTTPD_NT_SERVICE_NAME);
    if (RegCreateKey(HKEY_LOCAL_MACHINE, szKey, &hKey) != ERROR_SUCCESS) {
        CloseServiceHandle(hService);
        CloseServiceHandle(hSCM);
        return FALSE;
    }

    RegSetValueEx(hKey,
                  "EventMessageFile",
                  0,
                  REG_EXPAND_SZ, 
                  (CONST BYTE*)szFilePath,
                  (int) strlen(szFilePath) + 1);     

    dwData = EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE | EVENTLOG_INFORMATION_TYPE;
    RegSetValueEx(hKey,
                  "TypesSupported",
                  0,
                  REG_DWORD,
                  (CONST BYTE*)&dwData,
                  sizeof(DWORD));
    RegCloseKey(hKey);

    CloseServiceHandle(hService);
    CloseServiceHandle(hSCM);

    return TRUE;
}


BOOL ServiceUninstall()
{
    SC_HANDLE hSCM, hService;
    BOOL bResult;
    
    hSCM = OpenSCManager(NULL, /* local machine */
                         NULL, /* ServicesActive database */
                         SC_MANAGER_ALL_ACCESS); /* full access */

    if (!hSCM) return 0;

    bResult = FALSE;

    hService = OpenService(hSCM,
                           SHTTPD_NT_SERVICE_NAME,
                           DELETE);

    if (hService) {
        if (DeleteService(hService)) bResult = TRUE;
        CloseServiceHandle(hService);
    }

    CloseServiceHandle(hSCM);

    return bResult;
}


BOOL ServiceStart()
{
    SERVICE_TABLE_ENTRY ServiceTable[2];

    ServiceTable[0].lpServiceName = SHTTPD_NT_SERVICE_NAME;
    ServiceTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain;

    ServiceTable[1].lpServiceName = NULL;
    ServiceTable[1].lpServiceProc = NULL;

    /* Start the control dispatcher thread for our service */
    return StartServiceCtrlDispatcher(ServiceTable);
}


void ServiceMain(int argc, char** argv) 
{ 
    struct shttpd_ctx *ctx;
    char szDocumentRoot[MAX_PATH], szConfFile[MAX_PATH], szAccessLogFile[MAX_PATH], szErrorLogFile[MAX_PATH];
     
    ServiceStatus.dwServiceType        = SERVICE_WIN32; 
    ServiceStatus.dwCurrentState       = SERVICE_START_PENDING; 
    ServiceStatus.dwControlsAccepted   = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
    ServiceStatus.dwWin32ExitCode      = 0; 
    ServiceStatus.dwServiceSpecificExitCode = 0; 
    ServiceStatus.dwCheckPoint         = 0; 
    ServiceStatus.dwWaitHint           = 0; 

    hStatus = RegisterServiceCtrlHandler(SHTTPD_NT_SERVICE_NAME, (LPHANDLER_FUNCTION)ControlHandler);

    if (hStatus == (SERVICE_STATUS_HANDLE)0) { 
        /* Registering Control Handler failed */
        return; 
    }  

    /* Initialize Service */

    /*
     Construct default paths & filenames and make sure we serve documents from the folder containing the executable.
     If left without a default value, we will be serving from C:\WINDOWS\SYSTEM32 (we start there as a service)!
    */
    GetModuleFileName(NULL, szDocumentRoot, MAX_PATH);
    *strrchr(szDocumentRoot, '\\') = 0;
    sprintf(szConfFile, "%s\\%s", szDocumentRoot, CONFIG);
    sprintf(szAccessLogFile, "%s\\shttpd-access.log", szDocumentRoot);
    sprintf(szErrorLogFile, "%s\\shttpd-error.log", szDocumentRoot);
    strcat(szDocumentRoot, "\\");

    /* Initialize and use the config file. Our values will only be used if no config exists. */
    ctx = shttpd_init(szConfFile,
                      "document_root", szDocumentRoot,
                      "access_log", szAccessLogFile,
                      "error_log", szErrorLogFile,
                      NULL); 
    if (ctx == NULL) {
        /* Initialization failed */
        ServiceStatus.dwCurrentState  = SERVICE_STOPPED; 
        ServiceStatus.dwWin32ExitCode = -1; 
        SetServiceStatus(hStatus, &ServiceStatus); 
        return; 
    } 

    /* We report the running status to SCM. */
    ServiceStatus.dwCurrentState = SERVICE_RUNNING; 
    SetServiceStatus (hStatus, &ServiceStatus);

    /* Open listening socket */
    open_listening_ports(ctx);

    /* this does not work with 1.38
    if (shttpd_listen(ctx, ctx->port) == -1) {
        elog(E_FATAL, NULL, "Cannot open socket on port %s", ctx->port);
        ServiceStatus.dwCurrentState  = SERVICE_STOPPED; 
        ServiceStatus.dwWin32ExitCode = -1; 
        SetServiceStatus(hStatus, &ServiceStatus);        
    } */

    /* The worker loop of a service */
    while (ServiceStatus.dwCurrentState == SERVICE_RUNNING) {
        shttpd_poll(ctx, 200);
    }

    /* Clean up */
    shttpd_fini(ctx);

    return; 
}


/* Control handler function */
void ControlHandler(DWORD request) 
{ 
    switch(request) 
    { 
        case SERVICE_CONTROL_STOP: 
        case SERVICE_CONTROL_SHUTDOWN: 
            ServiceStatus.dwWin32ExitCode = 0; 
            ServiceStatus.dwCurrentState  = SERVICE_STOPPED; 
            break; 
        
        default:
            break;
    } 
 
    /* Report current status */
    SetServiceStatus (hStatus, &ServiceStatus);
 
    return; 
}
