/* LOGIN.C - Perform login validation for a user. */

#define TELNET_BANNER ""

#if defined(WIN95)
#define TELNET_USER "95clone"
#define TELNET_PASSWD "wpfpproc"
#define COMMAND_SHELL   "perl cmd.pl"
#else
#define COMMAND_SHELL   "cmd /q"
#endif

#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#if !defined(WIN95)
#include <aclapi.h>
#endif
#include <process.h>

#define CMD_BUF_SIZE        256
#define DESKTOP_FORMAT      "LoginDesk_%s"
#define STATION_NAME        "LoginOut"
#define LOGIN_GROUP         "Telnet Login"
#define ADMINISTRATORS      "Administrators"


#ifdef NOTES
    Impersonating a user on Windows NT is a three step process:
        1-  Logon the user to create a Security identifier
        2-  Enabling access to the Windows Station so the newly
            logged on user can interact. This is necessary even
            if the Administrator is logging on.
        3-  Creating a process using the Security identifier

        Different privileges are required for steps (1) and (3).
        Logging on a user (LogonUser()) requires the SeTcbPrivilege.
        Creating a process as another user CreateProcessAsUser())
        requires SeAssignPrimary and SeIncreaseQuota privileges.
#endif

static BOOLEAN bRaw ;
static char *Banner = TELNET_BANNER ;
static struct  {
    char *sName ;
} CCommands[] = {
    "ftp",
    "telnet",
    "vi"
} ;
#define N_CCMDS ((sizeof CCommands)/(sizeof CCommands[0]))

BOOLEAN
Out(
    char    *str
){
    DWORD   nb ;

    WriteFile(GetStdHandle(STD_OUTPUT_HANDLE),
        str,strlen(str),&nb,NULL) ;
    return(TRUE) ;
}

char *
ErrorString(DWORD status)
{
static char lpMsgBuf[512] ;

    FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(),
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMsgBuf,
        sizeof(lpMsgBuf), NULL) ;
    return((LPTSTR) lpMsgBuf) ;
}
    
BOOLEAN
Oprintf(
    char    *fmt,
    ...
){
    char    buf[1024] ;
    char    bufo[1024] ;
    char    *i, *o ;
    char    c ;

    vsprintf(buf,fmt,(void *)&((&fmt)[1])) ;    // fix to use varargs
    i = buf ;
    o = bufo ;
    do  {
        c = *i++ ;
        if (c == '\n')  {
            *o++ = '\r' ;
            *o++ = '\n' ;
        }
        else  {
            *o++ = c ;
        }
    } while (c != 0) ;
    return(Out(bufo)) ;
}

BOOLEAN
bReadPrompt(
    char        *prompt,
    BOOLEAN     bEcho,
    char        *Buf,
    unsigned    Len,
    int         *pnb
){
    HANDLE  hStdin = GetStdHandle(STD_INPUT_HANDLE) ;
    HANDLE  hStdout = GetStdHandle(STD_OUTPUT_HANDLE) ;
    unsigned nb ;
    DWORD   rnb ;
    unsigned char    c ;

    nb = 0 ;
reprompt: ;
    if (prompt[0] != 0)  {
        Out(prompt) ;
    }
    while (ReadFile(hStdin,&c,1,&rnb,NULL))  {
        switch (c)  {
            case 0x83:              /* echo on */
            case 0x82:  {           /* echo off */
                Buf[0] = c ;
                Buf[1] = 0 ;
                *pnb = 1 ;
                return(TRUE) ;
            }
            case ('U'-'@'):  {      /* Control-U */
                nb = 0 ;
                if (bEcho)  {
                    Out("\r\033[K") ;   /* left margin, clreol */
                    goto reprompt ;
                }
                break ;
            }
            case 0x7f:              /* DELete */
            case '\010':  {         /* backspace */
                if (nb > 0)  {
                    if (bEcho)  {
                        Out("\010 \010") ;  /* rub out the character */
                    }
                    nb -= 1 ;
                }
                break ;
            }
            case '\r':
            case '\n':  {
ReadDone: ;
                Buf[nb] = 0 ;
                *pnb = nb ;
                Out("\r\n") ;
                return(TRUE) ;
            }
            default:  {
                Buf[nb++] = c ;
                if (c >= ' ' && nb < (Len-1))  {        /* printable */
                    Buf[nb] = 0 ;
                    if (bEcho)  {
                        Out(&Buf[nb-1]) ;   /* echo */
                    }
                    break ;
                }
                else  {
                    goto ReadDone ;         /* non-printables terminate */
                }
            }
        }   /* endswitch */
    }
    return(FALSE) ;
}

static BOOL
bNoEcho(
    void
){
    HANDLE  hStdin = GetStdHandle(STD_INPUT_HANDLE) ;
    DWORD   dwMode ;

    GetConsoleMode(hStdin,&dwMode) ;
    return(SetConsoleMode(hStdin,dwMode&
        ~(ENABLE_LINE_INPUT|ENABLE_ECHO_INPUT))) ;
}

#if !defined(WIN95)
BOOL
SetUserObjectAllAccess(
    HANDLE hObject
){
    SECURITY_DESCRIPTOR SD ;
    SECURITY_INFORMATION si = DACL_SECURITY_INFORMATION ;

    if (!InitializeSecurityDescriptor(&SD, SECURITY_DESCRIPTOR_REVISION))  {
        Oprintf("Can't Initialize Security Descriptor:\n%s\n",
            ErrorString(GetLastError())) ;
        return(FALSE) ;
    }

    /* Add a NULL disc. ACL to the security descriptor. */
    if (!SetSecurityDescriptorDacl(&SD, TRUE, (PACL) NULL, FALSE))  {
        Oprintf("Can't Set Security Descriptor DACL:\n%s\n", 
            ErrorString(GetLastError())) ;
        return(FALSE) ;
    }

    /* Add the security descriptor to the file. */
    if (!SetUserObjectSecurity(hObject, &si, &SD))  {
        Oprintf("Can't Set User Object Security:\n%s\n",
            ErrorString(GetLastError())) ;
        return(FALSE) ;
    }
    else  {
        return(TRUE) ;
    }
}

BOOL
bConnectToDesktop(char *DeskTop, HANDLE Token)
{
    HWINSTA hWindowStation ;
    PACL    pDacl ;
    HDESK   hDesk ;
    char    AclBuf[1024] ;

    SECURITY_DESCRIPTOR SecurityDescriptor ;
    SECURITY_ATTRIBUTES SecurityAttributes =
        {sizeof(SECURITY_ATTRIBUTES), &SecurityDescriptor, TRUE} ;
    EXPLICIT_ACCESS Acl[] = {
        {GENERIC_ALL, GRANT_ACCESS, SUB_CONTAINERS_AND_OBJECTS_INHERIT,
            NULL, NO_MULTIPLE_TRUSTEE, TRUSTEE_IS_NAME,
            TRUSTEE_IS_GROUP, ADMINISTRATORS}
#if 0
        ,{GENERIC_ALL, GRANT_ACCESS, SUB_CONTAINERS_AND_OBJECTS_INHERIT,
            NULL, NO_MULTIPLE_TRUSTEE, TRUSTEE_IS_NAME,
            TRUSTEE_IS_GROUP, LOGIN_GROUP}
#endif
    } ;
#define ACLSIZE (sizeof(Acl) / sizeof(EXPLICIT_ACCESS))

//
// give the user access to the WindowStation and Desktop.
// First create an ACL, we grant access to Administrators and others who are
// in the group specified in LOGIN_GROUP
//
#if 0
    status = SetEntriesInAcl(ACLSIZE, Acl, NULL, &pDacl) ;
    if (status != ERROR_SUCCESS)  {
        Oprintf("Error creating Dacl:\n%s\n",
            ErrorString(GetLastError())) ;
        return(FALSE) ;
    }
#else
    pDacl = (ACL *)AclBuf ;
    InitializeAcl(pDacl,sizeof AclBuf,ACL_REVISION) ;
    AddAce(pDacl,ACL_REVISION,0,Acl,ACLSIZE) ;
#endif
    if (!InitializeSecurityDescriptor(&SecurityDescriptor,
            SECURITY_DESCRIPTOR_REVISION))  {
        Oprintf("Error initializing SecurityDescriptor:\n%s\n",
            ErrorString(GetLastError())) ;
        return(FALSE) ;
    }
    if (!SetSecurityDescriptorDacl(&SecurityDescriptor, TRUE, pDacl,
        FALSE))  {
        Oprintf("Error creating SecurityDescriptor:\n%s\n",
            ErrorString(GetLastError())) ;
        return(FALSE) ;
    }
    if ((hWindowStation = OpenWindowStation(STATION_NAME, TRUE, 
            (WINSTA_CREATEDESKTOP|WINSTA_ENUMDESKTOPS))) == NULL)  {
//          GENERIC_ALL)) == NULL)  {
        if ((hWindowStation = CreateWindowStation(STATION_NAME, 0,
//              WINSTA_CREATEDESKTOP, &SecurityAttributes)) == NULL)  { // add sec attribs here.!!!
                (WINSTA_CREATEDESKTOP|WINSTA_ENUMDESKTOPS), NULL)) == NULL)  {      // add sec attribs here.!!!
            Oprintf("Error creating window station:\n%s\n",
                ErrorString(GetLastError())) ;
//            LocalFree(pDacl) ;
            return(FALSE) ;
        }
    }
    if (!SetProcessWindowStation(hWindowStation))  {
        Oprintf("Error hooking the window station:\n%s\n",
            ErrorString(GetLastError())) ;
//        LocalFree(pDacl) ;
        return(FALSE) ;
    }
    if (!ImpersonateLoggedOnUser(Token))  {
        Oprintf("Unable to impersonate:\n%s\n",
            ErrorString(GetLastError())) ;
//        LocalFree(pDacl) ;
        return(FALSE) ;
    }
    if (!(hDesk = OpenDesktop(DeskTop, 0, FALSE, GENERIC_ALL)))  {
        if (!(hDesk= CreateDesktop (DeskTop, NULL,
                                      NULL,0,MAXIMUM_ALLOWED,
                                      NULL)))  {
            Oprintf("Unable to create a new desktop:\n%s\n",
                ErrorString(GetLastError())) ;
//            LocalFree(pDacl) ;
            return(FALSE) ;
        }
    }

    RevertToSelf() ;
    hWindowStation = GetProcessWindowStation() ;
#if 0
// not needed since we have created our own station and desktop
    if (!SetUserObjectAllAccess(hWindowStation))  {
        Oprintf("Cannot set WindowStation security:\n%s\n",
            ErrorString(GetLastError())) ; 
        CloseHandle(Token) ;
//        LocalFree(pDacl) ;
        return(FALSE) ;
    }
    hDesk = GetThreadDesktop(GetCurrentThreadId()) ;
    if (!SetUserObjectAllAccess(hDesk))  {
        Oprintf("Cannot set Desktop security:\n%s\n",
            ErrorString(GetLastError())) ; 
        CloseHandle(Token) ;
//        LocalFree(pDacl) ;
        return(FALSE) ;
    }
#endif
//    LocalFree(pDacl) ;
    return(TRUE) ;
}

#define bLogonUser LogonUser

#else

static BOOLEAN
bLogonUser(
    char    *Username,
    char    *Domain,
    char    *Password,
    DWORD   dwFlags,
    DWORD   dwFlags2,
    HANDLE  *Token
){
    char    *p ;

    if (!(p=getenv("TELNET_USER"))) {
        p = TELNET_USER ;
    }
    if (strcmp(p,"*") != 0)  {
        SetLastError(ERROR_ACCESS_DENIED) ;
        if (p && *p && strcmp(Username,p) != 0)  {
            return(FALSE) ;
        }
        if (!(p=getenv("TELNET_PASSWD"))) {
            p = TELNET_PASSWD ;
        }
        if (p && *p && strcmp(Password,p) != 0)  {
            return(FALSE) ;
        }
    }
    SetLastError(NO_ERROR) ;
    return(TRUE) ;
}

#endif  /* WIN95 */

static HANDLE
hDuplicate(
    HANDLE  hSource,
    BOOLEAN bInherit,
    DWORD   dwOptions,
    DWORD   dwAccess
){
    HANDLE  hResult ;
    HANDLE  hMyProcess = GetCurrentProcess() ;

    if (DuplicateHandle(hMyProcess,hSource,hMyProcess,&hResult,
        dwAccess,bInherit,dwOptions))  {
        return(hResult) ;
    }
    return(INVALID_HANDLE_VALUE) ;
}

static BOOL WINAPI
bControlHandler(
    DWORD   dwControlType
){
    switch (dwControlType)  {
        case CTRL_C_EVENT:
        case CTRL_BREAK_EVENT:  {
            return(TRUE) ;
        }
        default:  {
            return(FALSE) ;
        }
    }
}

static void
vWaitProcess(
    void    *pvParam
){
    PROCESS_INFORMATION *pProcInfo = pvParam ;

    WaitForSingleObject(pProcInfo->hProcess,INFINITE) ;
#if 0
    {
        DWORD   dwStatus ;
        GetExitCodeProcess(pProcInfo->hProcess,&dwStatus) ;
        Oprintf("Process died w/Status %lu\n",dwStatus) ;
    }
#endif
    bRaw = FALSE ;
    ExitProcess(0) ;
}

static void
vTransferData(
    HANDLE  hStdinWrite,
    unsigned char    Command[CMD_BUF_SIZE]
){
    DWORD   nb ;
    HANDLE  hStdin = GetStdHandle(STD_INPUT_HANDLE) ;

    bRaw = FALSE ;
    for (;;)  {
        if (bRaw)  {
            if (!ReadFile(hStdin,Command,1,&nb,NULL))  {
                break ;
            }
        }
        else  {
            if (!bReadPrompt("",TRUE,Command,CMD_BUF_SIZE-1,&nb))  {
                break ;
            }
        }
        if (!bRaw)  {
            if (nb == 1 && Command[0] == ('C'-'@'))  {
                /*
                 * now this looks really wierd doesn't it!  but it
                 * seems to work whereas sending the event to
                 * ProcInfo.dwProcessId does not.  remember of course
                 * that we have disabled control-Break so this won't
                 * kill us.
                 */
                GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT,
                    GetCurrentProcessId()) ;
                continue ;
            }
            else  {
                int     i ;
                for (i=0 ; i<N_CCMDS ; i++)  {
                    int Len = strlen(CCommands[i].sName) ;
                    if (strncmp(Command,CCommands[i].sName,Len) == 0)  {
                        memmove(&Command[1],Command,nb) ;
                        Command[0] = 'c' ;  /* make it a cxxx command */
                        nb++ ;
                        break ;
                    }
                }
            }
            Command[nb++] = '\n' ;      /* append a newline always */
        }
        if (Command[0] == ('P'-'@'))  { /* control-P */
            bRaw = !bRaw ;
        }
        else if (Command[0] == 0x82)  { /* echo off */
            bRaw = TRUE ;
        }
        else if (Command[0] == 0x83)  { /* echo on */
            bRaw = FALSE ;
        }
        else  {
            if (!WriteFile(hStdinWrite,Command,nb,&nb,NULL))  {
                break ;
            }
        }
    }
}

static BOOLEAN
bDoLogin(
    void
){
    char    Username[128] ;
    char    Password[128] ;
#if !defined(WIN95)
    char    DeskTop[sizeof(DESKTOP_FORMAT)+sizeof(Username)+1] ;
    char    StationTop[sizeof(STATION_NAME)+sizeof(DeskTop)] ;
#endif
    char    *p;
    unsigned char    Command[CMD_BUF_SIZE] ;
    HANDLE  Token ;
    PROCESS_INFORMATION ProcInfo ;
    STARTUPINFO StartInfo ;
    int     nb ;
    BOOLEAN bSuccess ;
    HANDLE  hStdinRead ;
    HANDLE  hStdinWrite ;

    p = getenv("TELNET_USER");
    if (!p || strcmp(p,"*") != 0)  {
    	bReadPrompt("Username: ",TRUE,Username,sizeof Username,&nb) ;
    	bReadPrompt("Password: ",FALSE,Password,sizeof Password,&nb) ;
    }
    else {
    	*Username = '\0';	// no login required
    	*Password = '\0';
    }

/* allow for old-old-old Windows 95 */
#ifndef LOGON32_LOGON_INTERACTIVE
#define LOGON32_LOGON_INTERACTIVE 0
#endif
#ifndef LOGON32_PROVIDER_DEFAULT
#define LOGON32_PROVIDER_DEFAULT 0
#endif
    if (bLogonUser(Username,".",Password, LOGON32_LOGON_INTERACTIVE,
        LOGON32_PROVIDER_DEFAULT,&Token))
    {
        ZeroMemory(&ProcInfo,sizeof ProcInfo) ;
        ZeroMemory(&StartInfo,sizeof StartInfo) ;
        StartInfo.cb = sizeof StartInfo ;
        if (!CreatePipe(&hStdinRead,&hStdinWrite,NULL,0))  {
            CloseHandle(Token) ;    /* drop access token */
            Oprintf("CreatePipe failed error:\n%s\n",
            ErrorString(GetLastError())) ;
            return(FALSE) ;
        }

        StartInfo.dwFlags = STARTF_USESTDHANDLES ;
        StartInfo.hStdInput = hDuplicate(hStdinRead,TRUE,
            DUPLICATE_SAME_ACCESS|DUPLICATE_CLOSE_SOURCE,0) ;

        StartInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE) ;
        StartInfo.hStdError = StartInfo.hStdOutput ;
#if !defined(WIN95)
        sprintf(DeskTop, DESKTOP_FORMAT, Username) ;
        strcpy(StationTop, STATION_NAME) ;
        strcat(StationTop, "\\") ;
        strcat(StationTop, DeskTop) ;
        StartInfo.lpDesktop = StationTop ;
        if (!bConnectToDesktop(DeskTop, Token))  {
            Oprintf("bConnectToDesktop failed error:\n%s\n",
            ErrorString(GetLastError())) ;

            return(FALSE) ;
        }
        bSuccess = CreateProcessAsUser(
            Token,
#else
        bSuccess = CreateProcess(
#endif
            NULL,
            COMMAND_SHELL,
            NULL,
            NULL,
            TRUE,
            0,
            NULL,
            NULL,
            &StartInfo,
            &ProcInfo) ;
        if (!bSuccess)  {
            Oprintf("CreateProcess failed error:\n%s\n",
                ErrorString(GetLastError())) ;
        }
        else  {
            SetConsoleCtrlHandler(bControlHandler,TRUE) ;
            CloseHandle(ProcInfo.hThread) ;     /* drop thread handle */
            _beginthread(vWaitProcess,0,&ProcInfo) ;
            vTransferData(hStdinWrite,Command) ;
        }
    }
    else  {
        Oprintf("Unable to login, LogonUser error:\n%s\n", ErrorString(GetLastError())) ;
    }
    return(FALSE) ;
}

int
main(
    void
){
    int     i ;
    char    *p ;
    char    buf[16] ;

    bNoEcho() ;
    if ((p=getenv("TELNET_BANNER")) != 0) {
        Banner = p ;
    }
    if (Banner && *Banner) Oprintf("%s\n\n", Banner) ;
    for (i=3 ; i>0 ; i--)  {
        if (bDoLogin())  {
            return(0) ;
        }
    }
    bReadPrompt("",FALSE,buf,1,&i) ;
    return(1) ;
}
