/* Windows Internet Kermit Service Daemon (WIKSD):
 * Copyright (C) 1985, 2003, Trustees of Columbia University in the City of New
 * York.  All rights reserved.
 *  
 * PERMISSIONS:
 *  
 *   The WIKSD software may be obtained directly, in binary form only, from
 *   the Kermit Project at Columbia University by any individual for his or
 *   her OWN USE, and by any company or other organization for its own
 *   INTERNAL DISTRIBUTION and use, including installation on servers that
 *   are accessed by customers or clients, WITHOUT EXPLICIT LICENSE.  All
 *   other forms of redistribution must be licensed from the Kermit Project
 *   at Columbia University.  These permissions apply only to the nonsecure
 *   version of WIKSD.
 *  
 * DISCLAIMER:
 *  
 *   THIS SOFTWARE IS PROVIDED AS IS, WITHOUT REPRESENTATION FROM THE
 *   TRUSTEES OF COLUMBIA UNIVERSITY IN THE CITY OF NEW YORK AS TO ITS
 *   FITNESS FOR ANY PURPOSE, AND WITHOUT WARRANTY BY THE TRUSTEES OF
 *   COLUMBIA UNIVERSITY IN THE CITY OF NEW YORK OF ANY KIND, EITHER
 *   EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED
 *   WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 *   THE TRUSTEES OF COLUMBIA UNIVERSITY IN THE CITY OF NEW YORK SHALL NOT
 *   BE LIABLE FOR ANY DAMAGES, INCLUDING SPECIAL, INDIRECT, INCIDENTAL,
 *   OR CONSEQUENTIAL DAMAGES, WITH RESPECT TO ANY CLAIM ARISING OUT OR IN
 *   CONNECTION WITH THE USE OF THE SOFTWARE, EVEN IF IT HAS BEEN OR IS
 *   HEREAFTER ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.  YOU SHALL
 *   INDEMNIFY AND HOLD HARMLESS THE TRUSTEES OF COLUMBIA UNIVERSITY IN
 *   THE CITY OF NEW YORK, ITS EMPLOYEES AND AGENTS FROM AND AGAINST ANY
 *   AND ALL CLAIMS, DEMANDS, LOSS, DAMAGE OR EXPENSE (INCLUDING
 *   ATTORNEYS' FEES) ARISING OUT OF YOUR USE OF THIS SOFTWARE.
 *  
 * The above copyright notice, permissions notice, and disclaimer may not be
 * removed, altered, or obscured and shall be included in all copies of the
 * WIKSD software.  The Trustees of Columbia University in the City of
 * New York reserve the right to revoke this permission if any of the terms
 * of use set forth above are breached.
 *  
 * For further information, contact the Kermit Project, Columbia University,
 * 612 West 115th Street, New York NY 10025-7799, USA; Phone +1 (212) 854 3703,
 * Fax +1 (212) 662 6442, kermit@columbia.edu, http://www.columbia.edu/kermit/
 */

/* On Windows compile with the command line:
 *  
 *  cl /MT -DNT iksdsvc.c wsock32.lib advapi32.lib
 */

/* 
 * Data for the Windows Service IKSDSVC could be stored in:
 * 
 *   HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\IKSD\
 */

#include <stdio.h>

#ifdef NT
#include <windows.h>
#include <winsock.h>
#include <winsvc.h>
#include <process.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <io.h>
#define strdup _strdup
#define ltoa   _ltoa
#define unlink _unlink

#endif
#define CONFIG_FILE "iksd.cfg"

#define bzero(x,y) memset(x,0,y)
#define BSDSELECT

#ifndef CK_HAVE_INTPTR_T
/* Any windows compiler too old to support this will be 32-bits (or less) */
#ifndef _INTPTR_T_DEFINED
typedef int intptr_t;
#endif /* _INTPTR_T_DEFINED */
#define CK_HAVE_INTPTR_T
#endif /* CK_HAVE_INTPTR_T */

#define MAXPORTS 32
struct PORT
{
    short id ;
    SOCKET   lsocket ;
    SOCKET   asocket ;
    char  * k95cmd ;
    int   showcmd;
} ports[MAXPORTS] ;
int portcount = 0 ;

#define MAXCHILDREN 4096
struct CHILDREN
{
    HANDLE hProcess;
    SOCKET socket;
} children[MAXCHILDREN];
int childcount = 0;

SERVICE_STATUS          IKSDStatus;
SERVICE_STATUS_HANDLE   IKSDStatusHandle;
int iksd_started = 0;
int ready_to_accept = 0 ;
static struct hostent *host;
static struct sockaddr_in saddr;
static int saddrlen ;
#ifdef BSDSELECT
fd_set rfds;
struct timeval tv;
#endif /* BSDSELECT */
int tcpsrv_fd = -1, ttyfd = -1 ;

void  WINAPI IKSDStart (DWORD argc, LPTSTR *argv);
#ifdef CKT_NT31ONLY
#ifdef __WATCOMC__
VOID WINAPI IKSDCtrlHandler (DWORD opcode);
#else
/* Visual C++ 1.0 32-bit edition doesn't like the WINAPI */
VOID IKSDCtrlHandler (DWORD opcode);
#endif
#else
void  WINAPI IKSDCtrlHandler (DWORD opcode);
#endif
DWORD IKSDInitialization (DWORD argc, LPTSTR *argv, DWORD *specificError);
HANDLE StartKermit( SOCKET socket, char * cmdline, int ShowCmd, SOCKET * );
BOOL ParseStandardArgs(int argc, char* argv[]);

void
SvcDebugOut(LPSTR String,
#ifdef _WIN64
            intptr_t
#else
            DWORD
#endif
            Status)
{
#ifdef DEBUG
    CHAR  Buffer[1024];
    if (strlen(String) < 1024)
    {
        sprintf(Buffer, String, Status);
        OutputDebugStringA(Buffer);
    }
#endif /* DEBUG */
}

static SERVICE_TABLE_ENTRY   DispatchTable[] =
{
    { "IKSD", (LPSERVICE_MAIN_FUNCTION) IKSDStart },
    { NULL,   NULL      }
};

void
init_children(void)
{       
    int i;
    for ( i=0 ;i<MAXCHILDREN;i++ ) {
        children[i].hProcess = INVALID_HANDLE_VALUE;
        children[i].socket = (SOCKET)INVALID_HANDLE_VALUE;
    }   
}

void
kill_children(void)
{
    int i;

    for (i=0; i<MAXCHILDREN && childcount; i++) {
        if ( children[i].hProcess != INVALID_HANDLE_VALUE ) {
            SvcDebugOut("TerminateProcess  %d\n", (intptr_t)children[i].hProcess);
            TerminateProcess(children[i].hProcess, 1111);
            closesocket(children[i].socket);
            CloseHandle(children[i].hProcess);
            children[i].socket = (SOCKET)INVALID_HANDLE_VALUE;
            children[i].hProcess = INVALID_HANDLE_VALUE;
            childcount--;       
        }
    }
}

void
check_children(void)
{
    int i,found;
    DWORD dwExit;

    for (i=0, found=0; i<MAXCHILDREN && found<childcount; i++) {
        if ( children[i].hProcess != INVALID_HANDLE_VALUE ) {
            if (GetExitCodeProcess(children[i].hProcess, &dwExit)) {
                SvcDebugOut("GetExitCodeProcess ExitCode = %d\n",dwExit);
                if ( dwExit != STILL_ACTIVE ) {
                    SvcDebugOut("Closing socket and process handle\n",0);
                    closesocket(children[i].socket);
                    CloseHandle(children[i].hProcess);
                    children[i].socket = (SOCKET)INVALID_HANDLE_VALUE;
                    children[i].hProcess = INVALID_HANDLE_VALUE;
                    childcount--;       
                    /* Do not increase found if we reduce childcount */
                    continue;
                }
            }
            found++;
        }
    }
}

void
add_child(HANDLE hProcess, int socket)
{
    int i;

    while (childcount == MAXCHILDREN) {
        SvcDebugOut("Max Number of Children: Going to Sleep\n",0);
        Sleep(1000);
        check_children();
    }
    
    for ( i=0;i<MAXCHILDREN;i++ ) {
        if ( children[i].hProcess == INVALID_HANDLE_VALUE ) {
            SvcDebugOut("Child Added at %d\n",i);
            children[i].hProcess = hProcess;
            children[i].socket = socket;
            childcount++;
            return;
        }
    }
}

int
main(int argc, char **argv)
{
    init_children();

    if ( ParseStandardArgs(argc, argv) )
        return(0);

    if (!StartServiceCtrlDispatcher(DispatchTable)) {
            SvcDebugOut(" [IKSD] StartServiceCtrlDispatcher error = %d\n",
                         GetLastError());
            return(1);
    }
    return(0);
}

void
iksd_stopped()
{
    DWORD status;

    iksd_started = 0;
    // Initialization complete - report running status.
    IKSDStatus.dwCurrentState       = SERVICE_STOPPED;
    IKSDStatus.dwCheckPoint         = 0;
    IKSDStatus.dwWaitHint           = 0;

    if (!SetServiceStatus (IKSDStatusHandle, &IKSDStatus))
    {
        status = GetLastError();
        SvcDebugOut(" [IKSD] SetServiceStatus error %ld\n",status);
    }

    exit(1);
}

int
open_sockets(DWORD *specificError)
{
    int i;
    int on = 1;

    for ( i=0; i<portcount; i++)
    {
        /* Set up socket structure and get host address */

        bzero((char *)&saddr, sizeof(saddr));
        saddr.sin_family = AF_INET ;
        saddr.sin_addr.s_addr = INADDR_ANY ;

        /* Get a file descriptor for the connection. */

        saddr.sin_port = ports[i].id;

        if ((ports[i].lsocket = socket(AF_INET, SOCK_STREAM, 0)) < 0)
        {
            perror("TCP socket error");
            *specificError = 3;
            return 1;
        }
        errno = 0;

        /* Specify the Port may be reused */
        setsockopt(ports[i].lsocket, SOL_SOCKET, SO_REUSEADDR,(char *) &on, sizeof on);

        /* Now bind to the socket */
#ifdef DEBUG
        SvcDebugOut("Binding socket to port %d ...\n",
                ntohs((unsigned short)ports[i].id)) ;
#endif
        if (bind(ports[i].lsocket, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
        {
            i = errno;                  /* save error code */
            closesocket(ports[i].lsocket) ;
            ports[i].lsocket = -1 ;
            ports[i].asocket = -1;
            errno = i;                  /* and report this error */
            SvcDebugOut("tcpsrv_open bind errno %d\n",errno);
            *specificError = 4;
            return 1;
        }
#ifdef DEBUG
        SvcDebugOut("Listening ...\n",0);
#endif
        if (listen(ports[i].lsocket, 15) < 0)
        {
            i = errno;                  /* save error code */
            closesocket(ports[i].lsocket) ;
            ports[i].lsocket = -1 ;
            ports[i].asocket = -1;
            errno = i;                  /* and report this error */
            SvcDebugOut("tcpsrv_open listen errno %d\n",errno);
            *specificError = 5;
            return 1;
        }
    }
    return(0);
}

void
listen_thread( void * dummy )
{
    int i, j;
    int on = 1;
    HANDLE hProcess;
    SOCKET sockdup;

    SvcDebugOut("Servicing ports:\n",0);
    for ( i=0;i<portcount ;i++ )
    {
        SvcDebugOut("  %d\n", ntohs((unsigned short)ports[i].id) ) ;
    }

    saddrlen = sizeof(saddr) ;

#ifdef BSDSELECT
    tv.tv_sec  = 1L;
    tv.tv_usec = 0L;
#endif /* BSDSELECT */

    while ( iksd_started )
    {
        while ( !ready_to_accept && iksd_started )
        {
            check_children();

#ifdef BSDSELECT
            FD_ZERO(&rfds);
            for ( i=0; i<portcount;i++ )
                FD_SET(ports[i].lsocket, &rfds);
            if (select(FD_SETSIZE, &rfds, NULL, NULL, &tv ) > 0)
            {
                for ( i=0; i<portcount ; i++ )
                {
                    if ( ready_to_accept = FD_ISSET(ports[i].lsocket, &rfds) )
                        break;
                }
            }
#else /* BSDSELECT */
#ifdef IBMSELECT
???          /* in order to make this work, we need to create an array of socket values */
             ready_to_accept = (( select(&tcpsrv_fd, 1, 0, 0,1 )) == 1) ;
#endif /* IBMSELECT */
#endif /* BSDSELECT */
        }

        if ( ready_to_accept )
        {
            if ((ports[i].asocket = accept(ports[i].lsocket, (struct sockaddr *)&saddr,&saddrlen)) < 0)
            {
                i = errno;                      /* save error code */
                closesocket(ports[i].lsocket) ;
                ports[i].asocket = -1;
                ports[i].lsocket = -1 ;
                errno = i;                      /* and report this error */
                SvcDebugOut("tcpsrv_open accept errno %d\n",errno);
                iksd_stopped();
                return;
            }

            setsockopt(ports[i].asocket, SOL_SOCKET, SO_OOBINLINE,(char *) &on, sizeof on);

            if ((host = gethostbyaddr((char *)&saddr.sin_addr,4,PF_INET)) != NULL)
            {
                SvcDebugOut(host->h_name,0);
                SvcDebugOut(" connected on port %d\n",ntohs(ports[i].id)) ;
            }

            /* Now start subprocess */
            if ( ports[i].asocket && ports[i].k95cmd ) {
                SvcDebugOut("Starting IKSD with socket %d and command\n  ",ports[i].asocket);
                SvcDebugOut(ports[i].k95cmd,0);
                SvcDebugOut("\n",0);
            } else
                SvcDebugOut("Starting IKSD with socket %d\n",ports[i].asocket);
            hProcess = StartKermit(ports[i].asocket,ports[i].k95cmd, ports[i].showcmd, &sockdup);
            if ( hProcess != INVALID_HANDLE_VALUE ) 
                add_child(hProcess, sockdup);
            else /* we used CLOSE_SOURCE when duplicating */
                closesocket(ports[i].asocket);      
            ready_to_accept = 0;
            continue;                           /* Go get the next one */
        }
        else
        {
            j = errno;                  /* save error code */

            if ( !iksd_started ) {
                /* Close all ports */
                for ( i=0;i<portcount;i++ ) {
                    closesocket(ports[i].lsocket) ;
                    ports[i].lsocket = -1;
                    ports[i].asocket = -1;
                }
                iksd_stopped();
            } else {
                closesocket(ports[i].lsocket) ;
                ports[i].lsocket = -1;
                ports[i].asocket = -1;
            }
            errno = j;                  /* and report this error */
            SvcDebugOut("tcpsrv_open accept errno %d\n",errno);
            return;
        }
    }
}

void WINAPI
IKSDStart (DWORD argc, LPTSTR *argv)
{
    DWORD status;
    DWORD specificError;

    IKSDStatus.dwServiceType        = SERVICE_WIN32;
    IKSDStatus.dwCurrentState       = SERVICE_START_PENDING;
    IKSDStatus.dwControlsAccepted   = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;
    IKSDStatus.dwWin32ExitCode      = 0;
    IKSDStatus.dwServiceSpecificExitCode = 0;
    IKSDStatus.dwCheckPoint         = 0;
    IKSDStatus.dwWaitHint           = 0;

    IKSDStatusHandle = RegisterServiceCtrlHandler( "IKSD", IKSDCtrlHandler);

    if (IKSDStatusHandle == (SERVICE_STATUS_HANDLE)0)
    {
        SvcDebugOut(" [IKSD] RegisterServiceCtrlHandler failed %d\n",
                     GetLastError());
        return;
    }

    // Initialization code goes here.
    status = IKSDInitialization(argc,argv, &specificError);

    // Handle error condition
    if (status != NO_ERROR)
    {
        IKSDStatus.dwCurrentState       = SERVICE_STOPPED;
        IKSDStatus.dwCheckPoint         = 0;
        IKSDStatus.dwWaitHint           = 0;
        IKSDStatus.dwWin32ExitCode      = status;
        IKSDStatus.dwServiceSpecificExitCode = specificError;

        SetServiceStatus (IKSDStatusHandle, &IKSDStatus);
        return;
    }

    iksd_started = 1;
    _beginthread(listen_thread,65536, NULL);

    // Initialization complete - report running status.
    IKSDStatus.dwCurrentState       = SERVICE_RUNNING;
    IKSDStatus.dwCheckPoint         = 0;
    IKSDStatus.dwWaitHint           = 0;

    if (!SetServiceStatus (IKSDStatusHandle, &IKSDStatus))
    {
        status = GetLastError();
        SvcDebugOut(" [IKSD] SetServiceStatus error %ld\n",status);
    }

    // This is where the service does its work.
    SvcDebugOut(" [IKSD] Returning the Main Thread \n",0);

    return;
}

#ifdef CKT_NT31ONLY
#ifdef __WATCOMC__
VOID WINAPI
#else
VOID
#endif
#else
VOID WINAPI
#endif
IKSDCtrlHandler (DWORD Opcode)
{
    DWORD status;

    switch(Opcode)
    {
        case SERVICE_CONTROL_PAUSE:
        // Do whatever it takes to pause here.
            IKSDStatus.dwCurrentState = SERVICE_PAUSED;
            break;

        case SERVICE_CONTROL_CONTINUE:
        // Do whatever it takes to continue here.
            IKSDStatus.dwCurrentState = SERVICE_RUNNING;
            break;

        case SERVICE_CONTROL_STOP:
        // Do whatever it takes to stop here.
            IKSDStatus.dwWin32ExitCode = 0;
            IKSDStatus.dwCurrentState  = SERVICE_STOP_PENDING;
            IKSDStatus.dwCheckPoint    = 0;
            IKSDStatus.dwWaitHint      = 0;

            if (!SetServiceStatus (IKSDStatusHandle, &IKSDStatus))
            {
                status = GetLastError();
                SvcDebugOut(" [IKSD] SetServiceStatus error %ld\n",
                             status);
            }

            iksd_started = 0;
            kill_children();
            SvcDebugOut(" [IKSD] Leaving IKSD \n",0);
            return;

        case SERVICE_CONTROL_INTERROGATE:
        // Fall through to send current status.
            break;

        default:
            SvcDebugOut(" [IKSD] Unrecognized opcode %ld\n",
                Opcode);
    }

    // Send current status.
    if (!SetServiceStatus (IKSDStatusHandle,  &IKSDStatus))
    {
        status = GetLastError();
        SvcDebugOut(" [IKSD] SetServiceStatus error %ld\n",status);
    }
    return;
}


// Stub initialization function.
int
ParseCmdLine( int argc, char * argv[], DWORD * specificError )
{
    int len = 0, i ;
    char * p = NULL ;
    static struct servent *service, servrec;
    int arg = 1;

    /* Set up some defaults */
    ports[portcount].showcmd = SW_SHOWNORMAL;
    service = getservbyname("kermit", "tcp");

    /* Allocate the max needed memory */
    for ( i = 1 ; i < argc ; i++ )
        len += strlen( argv[i] ) + 1 ;

    ports[portcount].k95cmd = (char *) malloc( len+1 ) ;
    if ( !ports[portcount].k95cmd )
    {
        SvcDebugOut("memory allocation error\n",0) ;
        *specificError = 1;
        return 0;
    }
    ports[portcount].k95cmd[0] = '\0' ;

    /* Process each command line option */
    for ( i=1 ; i<argc ; i++ )
    {
        if ( argv[arg][0] == '-' && argv[arg][1] == '-' ) {
            if ( !_stricmp("--maximized",argv[arg]) ) {
                ports[portcount].showcmd = SW_SHOWMAXIMIZED;
                continue;
            }
            else if ( !_stricmp("--minimized",argv[arg]) ) {
                ports[portcount].showcmd = SW_SHOWMINIMIZED;
                continue;
            }
            else if ( !_stricmp("--normal",argv[arg]) ) {
                ports[portcount].showcmd = SW_NORMAL;
                continue;
            }
            else if ( !_stricmp("--minnoactive",argv[arg]) ) {
                ports[portcount].showcmd = SW_SHOWMINNOACTIVE;
                continue;
            }
            else if ( !_strnicmp("--port:",argv[arg],7) ) {
                p = &argv[arg][7];
                if (isdigit(*p)) {                      /* Use socket number without lookup */
                    service = &servrec;
                    service->s_port = htons((unsigned short)atoi(p));
                } else {                                /* Otherwise lookup the service name */
                    service = getservbyname(p, "tcp");
                }
                continue;
            }
        }

        if ( ports[portcount].k95cmd[0] )
            strcat( ports[portcount].k95cmd, " ");
        strcat( ports[portcount].k95cmd, argv[i] );
    }

    if ( service )
        ports[portcount].id = service->s_port;
    else
        ports[portcount].id = htons( 1649 );

    *specificError = 0;
    portcount++ ;
    return portcount ;
}

HANDLE
StartKermit( SOCKET socket, char * cmdline, int ShowCmd, SOCKET *psockdup )
{
#ifdef NT
   PROCESS_INFORMATION StartKermitProcessInfo ;
   STARTUPINFO si ;
   SOCKET sockdup = (SOCKET)INVALID_HANDLE_VALUE ;
   static HANDLE hCurrent = INVALID_HANDLE_VALUE;
   static char buf[512] ;

   memset( &si, 0, sizeof(STARTUPINFO) ) ;
   si.cb = sizeof(STARTUPINFO);
   si.dwFlags = STARTF_USESHOWWINDOW;
   si.wShowWindow = ShowCmd;
    
    if ( hCurrent == INVALID_HANDLE_VALUE )
        hCurrent = GetCurrentProcess();

    *psockdup = (SOCKET)INVALID_HANDLE_VALUE;

   if (!DuplicateHandle( hCurrent, (HANDLE) socket,
                    hCurrent, (LPHANDLE)&sockdup,
                    DUPLICATE_SAME_ACCESS, TRUE,
                    DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS ))
   {
       SvcDebugOut( "\nINTERNET KERMIT SERVICE DAEMON FATAL ERROR:\n",0 );
       SvcDebugOut(" You are using a WinSOCK which does not allow socket handles\n",0);
       SvcDebugOut(" to be duplicated or shared with child processes.\n\n",0);
       SvcDebugOut(" If you are attempting to start Kermit 95 Host Mode,\n",0);
       SvcDebugOut(" please change SESSIONS to 1 in your configuration.\n",0);
       SvcDebugOut("\n Press ENTER to exit...\n",0);
       fscanf( stdin, "" ) ;
       return(INVALID_HANDLE_VALUE) ;
   }

    /* First try IKSDNT.EXE */
#ifdef MSDEV
    strcpy( buf, "msdev.exe iksdnt.exe -# 132 -A " ) ;
#else
    strcpy( buf, "iksdnt.exe -# 132 -A " ) ;
#endif /* DEBUG */
#ifdef _WIN64
   _ui64toa((unsigned __int64)sockdup, buf+strlen(buf), 10);
#else
    ltoa( (LONG) sockdup, buf+strlen(buf), 10 ) ;
#endif
    strcat( buf, " " );
    strcat( buf, cmdline ) ;

    SvcDebugOut("Executing: ",0);
    SvcDebugOut(buf,0) ;
    SvcDebugOut("\n",0);

    if (CreateProcess(
                       (LPSTR)NULL,          /* start K-95  */
                       (LPSTR)buf, /* give it the file */
                       (LPSECURITY_ATTRIBUTES)NULL,
                       (LPSECURITY_ATTRIBUTES)NULL,
                        TRUE,                      /* fix if necessary */
                       (DWORD) CREATE_NEW_CONSOLE | CREATE_NEW_PROCESS_GROUP,
                       (LPVOID)NULL,                /* fix if necessary */
                       (LPSTR)NULL,               /* Current directory */
                       &si,                     /* Startup info, fix */
                       &StartKermitProcessInfo  /* Process info */
                      ))
    {
        CloseHandle(StartKermitProcessInfo.hThread);
        *psockdup = sockdup;
        return (StartKermitProcessInfo.hProcess);
    }

    /* If that fails then try K95.EXE */
#ifdef MSDEV
    strcpy( buf, "msdev.exe k95.exe -# 132 -A " ) ;
#else
    strcpy( buf, "k95.exe -# 132 -A " ) ;
#endif /* DEBUG */
#ifdef _WIN64
   _ui64toa((unsigned __int64)sockdup, buf+strlen(buf), 10);
#else
    ltoa( (LONG) sockdup, buf+strlen(buf), 10 ) ;
#endif
    strcat( buf, " " );
    strcat( buf, cmdline ) ;

    SvcDebugOut("Executing: \n",0);
    SvcDebugOut(buf,0) ;
    SvcDebugOut("\n",0);
    if (CreateProcess(
                       (LPSTR)NULL,          /* start K-95  */
                       (LPSTR)buf, /* give it the file */
                       (LPSECURITY_ATTRIBUTES)NULL,
                       (LPSECURITY_ATTRIBUTES)NULL,
                        TRUE,                      /* fix if necessary */
                       (DWORD) CREATE_NEW_CONSOLE | CREATE_NEW_PROCESS_GROUP,
                       (LPVOID)NULL,                /* fix if necessary */
                       (LPSTR)NULL,               /* Current directory */
                       &si,                     /* Startup info, fix */
                       &StartKermitProcessInfo  /* Process info */
                      ))
    {
        CloseHandle(StartKermitProcessInfo.hThread);
        *psockdup = sockdup;
        return (StartKermitProcessInfo.hProcess);
    }

    SvcDebugOut("CreateProcess() failed gle=%ul\n",GetLastError());
    CloseHandle((HANDLE)sockdup) ;
    return(INVALID_HANDLE_VALUE);
#else /* NT */
    Not built for OS/2 yet.
#endif /* NT */
}

#ifdef NT
#ifndef VER_PLATFORM_WIN32_NT
#define VER_PLATFORM_WIN32_NT 2
#endif /* VER_PLATFORM_WIN32_NT */
#endif /* NT */

BOOL IsWindowsNT() {
#ifdef NT
#ifdef CKT_NT35_OR_31
    DWORD dwVersion = GetVersion();
    if (dwVersion < 0x80000000) {
        return TRUE;
    } else {
        return FALSE;
    }
#else
    OSVERSIONINFO osverinfo ;
    osverinfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO) ;
    GetVersionEx( &osverinfo ) ;
    if (osverinfo.dwPlatformId == VER_PLATFORM_WIN32_NT) {
        return TRUE;
    } else {
        return FALSE;
    }
#endif /* CKT_NT35_OR_31 */
#else
    return FALSE;
#endif
};

// Stub initialization function.
DWORD
IKSDInitialization(DWORD   argc, LPTSTR  *argv,
    DWORD *specificError)
{
    char * dbdir=NULL, dbfile[256];
    int rc = 0;
#ifdef NT
    WSADATA data ;

    SvcDebugOut("Internet Kermit Service Daemon\n",0);
    rc = WSAStartup( MAKEWORD( 2, 0 ), &data ) ;
    if ( rc == WSAVERNOTSUPPORTED )
    {
      WSACleanup() ;
      rc = WSAStartup( MAKEWORD( 1, 1 ), &data ) ;
   }
#else /* NT */

#endif /* NT */

    if (ParseCmdLine( argc, argv, specificError ) == 0)
        return 1;

    if ( !portcount )
    {
#ifdef MSDEV
        ports[portcount].showcmd = SW_NORMAL;
#else
        ports[portcount].showcmd = SW_SHOWMINNOACTIVE;
#endif
        ports[portcount].k95cmd = (char *) malloc( 1 ) ;
        ports[portcount].k95cmd[0] = '\0' ;
        ports[portcount].id = htons( 1649 ) ;
        portcount++;
    }

#ifdef NT

    if (IsWindowsNT()) {
        dbdir = getenv("SystemRoot");
    } else {
        dbdir = getenv("winbootdir");
        if (!dbdir)
            dbdir = getenv("windir");
    }
    dbdir = getenv("SystemRoot");
    if (!dbdir)
        dbdir = "C:/";
#else /* NT */
    dbdir = "C:/";
#endif /* NT */
    sprintf(dbfile,"%s\\iksd.lck",dbdir);
    unlink(dbfile);
    sprintf(dbfile,"%s\\iksd.db",dbdir);
    unlink(dbfile);

    if ( open_sockets(specificError) )
        return(1);
    return 0;
}


BOOL
IsInstalled()
{
    BOOL bResult = FALSE;
    SC_HANDLE hSCM;
    SC_HANDLE hService;

    // Open the Service Control Manager
    hSCM = OpenSCManager( NULL, // local machine
                          NULL, // ServicesActive database
                          SC_MANAGER_ALL_ACCESS); // full access
    if (hSCM) {

        // Try to open the service
        hService = OpenService( hSCM,
                                "IKSD",
                                SERVICE_QUERY_CONFIG);
        if (hService) {
            bResult = TRUE;
            CloseServiceHandle(hService);
        }

        CloseServiceHandle(hSCM);
    }

    return bResult;
}

BOOL
Install()
{
    char szFilePath[_MAX_PATH];
    SC_HANDLE hSCM;
    SC_HANDLE hService;
    char szKey[256];
    HKEY hKey = NULL;
    DWORD dwData;

    // Open the Service Control Manager
    hSCM = OpenSCManager( NULL, // local machine
                          NULL, // ServicesActive database
                          SC_MANAGER_ALL_ACCESS); // full access
    if (!hSCM)
        return FALSE;

    // Get the executable file path
    GetModuleFileName(NULL, szFilePath, sizeof(szFilePath));

    // Create the service
    hService = CreateService( hSCM,
                              "IKSD",
                              "IKSD",
                              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;
    }

    // make registry entries to support logging messages
    // Add the source name as a subkey under the Application
    // key in the EventLog service portion of the registry.
    strcpy(szKey, "SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application\\IKSD");
    if (RegCreateKey(HKEY_LOCAL_MACHINE, szKey, &hKey) != ERROR_SUCCESS) {
        CloseServiceHandle(hService);
        CloseServiceHandle(hSCM);
        return FALSE;
    }

    // Add the Event ID message-file name to the 'EventMessageFile' subkey.
    RegSetValueEx( hKey,
                   "EventMessageFile",
                    0,
                    REG_EXPAND_SZ,
                    (CONST BYTE*)szFilePath,
                    strlen(szFilePath) + 1);

    // Set the supported types flags.
    dwData = EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE | EVENTLOG_INFORMATION_TYPE;
    RegSetValueEx( hKey,
                   "TypesSupported",
                    0,
                    REG_DWORD,
                    (CONST BYTE*)&dwData,
                     sizeof(DWORD));
    RegCloseKey(hKey);

    // LogEvent(EVENTLOG_INFORMATION_TYPE, EVMSG_INSTALLED, "IKSD");

    // tidy up
    CloseServiceHandle(hService);
    CloseServiceHandle(hSCM);
    return TRUE;
}

BOOL
Uninstall()
{
    BOOL bResult = FALSE;
    SC_HANDLE hService;
    SC_HANDLE hSCM;

    // Open the Service Control Manager
    hSCM = OpenSCManager( NULL, // local machine
                          NULL, // ServicesActive database
                          SC_MANAGER_ALL_ACCESS); // full access
    if (!hSCM)
        return FALSE;

    hService = OpenService( hSCM,
                            "IKSD",
                            DELETE);
    if (hService) {
        if (DeleteService(hService)) {
            // LogEvent(EVENTLOG_INFORMATION_TYPE, EVMSG_REMOVED, "IKSD");
            bResult = TRUE;
        } else {
            // LogEvent(EVENTLOG_ERROR_TYPE, EVMSG_NOTREMOVED, "IKSD");
        }
        CloseServiceHandle(hService);
    }

    CloseServiceHandle(hSCM);
    return bResult;
}

// Returns TRUE if it found an arg it recognised, FALSE if not
// Note: processing some arguments causes output to stdout to be generated.
BOOL
ParseStandardArgs(int argc, char* argv[])
{
    char szFilePath[_MAX_PATH];

    // See if we have any command line args we recognize
    if (argc <= 1)
        return FALSE;

    if ( _stricmp(argv[1], "-h") == 0 ||
         _stricmp(argv[1], "-?") == 0 ||
         _stricmp(argv[1], "/h") == 0 ||
         _stricmp(argv[1], "/?") == 0) {

        //
        GetModuleFileName(NULL, szFilePath, sizeof(szFilePath));
        printf("usage: %s [-v | -i | -u | -h]\r\n",szFilePath);
        return TRUE;
    } else if (_stricmp(argv[1], "-v") == 0 ||
               _stricmp(argv[1], "/v") == 0 ) {

        // Spit out version info
        printf("IKSD Version 0.1\n",0);
        printf("The service is %s installed\n",
               IsInstalled() ? "currently" : "not");
        return TRUE; // say we processed the argument

    } else if (_stricmp(argv[1], "-i") == 0 ||
               _stricmp(argv[1], "/i") == 0) {

        // Request to install.
        if (IsInstalled()) {
            printf("%s is already installed\n", "IKSD");
        } else {
            // Try and install the copy that's running
            if (Install()) {
                printf("%s installed\n", "IKSD");
            } else {
                printf("%s failed to install. Error %d\n", "IKSD", GetLastError());
            }
        }
        return TRUE; // say we processed the argument

    } else if (_stricmp(argv[1], "-u") == 0 ||
               _stricmp(argv[1], "/u") == 0) {

        // Request to uninstall.
        if (!IsInstalled()) {
            printf("%s is not installed\n", "IKSD");
        } else {
            // Try and remove the copy that's installed
            if (Uninstall()) {
                // Get the executable file path
                GetModuleFileName(NULL, szFilePath, sizeof(szFilePath));
                printf("%s removed. (You must delete the file (%s) yourself.)\n",
                       "IKSD", szFilePath);
            } else {
                printf("Could not remove %s. Error %d\n", "IKSD", GetLastError());
            }
        }
        return TRUE; // say we processed the argument

    }

    // Don't recognise the args
    return FALSE;
}
