// Copyright (c) 1995, Microsoft Corporation, all rights reserved // // terminal.c // Remote Access Common Dialog APIs // Terminal dialogs // // 08/28/95 Steve Cobb #include "rasdlgp.h" #include "rasscrpt.h" #define WM_EOLFROMDEVICE (WM_USER+999) #define SECS_ReceiveTimeout 1 #define SIZE_ReceiveBuf 1024 #define SIZE_SendBuf 1 //---------------------------------------------------------------------------- // Help maps //---------------------------------------------------------------------------- static DWORD g_adwItHelp[] = { CID_IT_EB_Screen, HID_IT_EB_Screen, CID_IT_ST_IpAddress, HID_IT_CC_IpAddress, CID_IT_CC_IpAddress, HID_IT_CC_IpAddress, IDOK, HID_IT_PB_Done, 0, 0 }; //---------------------------------------------------------------------------- // Local datatypes (alphabetically) //---------------------------------------------------------------------------- // Interactive terminal dialog argument block. // typedef struct _ITARGS { DWORD sidTitle; TCHAR* pszIpAddress; HRASCONN hrasconn; PBENTRY* pEntry; RASDIALPARAMS* pRdp; } ITARGS; // Interactive terminal dialog context block. // typedef struct _ITINFO { // Caller's arguments to the dialog. // ITARGS* pArgs; // Handle of this dialog and some of it's controls. // HWND hwndDlg; HWND hwndEbScreen; HWND hwndCcIpAddress; HWND hwndPbBogus; // Set when waiting for the thread to terminate. // BOOL fAbortReceiveLoop; // Original dialog and screen edit box window proc. // WNDPROC pOldWndProc; WNDPROC pOldEbScreenWndProc; // buffers for RasScriptSend/RasScriptReceive. // BYTE pbyteReceiveBuf[SIZE_ReceiveBuf]; BYTE pbyteSendBuf[SIZE_SendBuf]; // handle to active script on this connection // HANDLE hscript; // Screen edit box font and brush. // HFONT hfontEbScreen; HBRUSH hbrEbScreen; } ITINFO; //---------------------------------------------------------------------------- // Local prototypes (alphabetically) //---------------------------------------------------------------------------- INT_PTR CALLBACK ItDlgProc( IN HWND hwnd, IN UINT unMsg, IN WPARAM wparam, IN LPARAM lparam ); BOOL ItCommand( IN ITINFO* pInfo, IN WORD wNotification, IN WORD wId, IN HWND hwndCtrl ); LRESULT APIENTRY ItEbScreenWndProc( HWND hwnd, UINT unMsg, WPARAM wParam, LPARAM lParam ); BOOL ItInit( IN HWND hwndDlg, IN ITARGS* pArgs ); BOOL ItRasApiComplete( IN ITINFO* pInfo ); DWORD ItReceiveMonitorThread( LPVOID pThreadArg ); VOID ItTerm( IN HWND hwndDlg ); VOID ItViewScriptLog( IN HWND hwndOwner ); LRESULT APIENTRY ItWndProc( HWND hwnd, UINT unMsg, WPARAM wParam, LPARAM lParam ); //---------------------------------------------------------------------------- // Terminal dialog // Listed alphabetically following stub API and dialog proc //---------------------------------------------------------------------------- BOOL TerminalDlg( IN PBENTRY* pEntry, IN RASDIALPARAMS* pRdp, IN HWND hwndOwner, IN HRASCONN hrasconn, IN DWORD sidTitle, IN OUT TCHAR* pszIpAddress ) // Pops-up the Terminal dialog. 'HwndOwner' is the window owning the // dialog. 'Hrasconn' is the RAS connection handle to talk on. // 'SidTitle' is ID of the string displayed as the window caption. // 'PszIpAddress' is caller's buffer of at least 16 characters containing // the initial IP address on entry and the edited IP address on exit. If // 'pszIpAddress' is empty, no IP address field is displayed. // // Returns true if user pressed OK and succeeded, false if he pressed // Cancel or encountered an error. // { INT_PTR nStatus; INT nDlg; ITARGS args; TRACE( "TerminalDlg" ); if (pszIpAddress && pszIpAddress[ 0 ]) { InitCommonControls(); IpAddrInit( g_hinstDll, SID_PopupTitle, SID_BadIpAddrRange ); nDlg = DID_IT_SlipTerminal; } else { nDlg = DID_IT_Terminal; } args.pszIpAddress = pszIpAddress; args.sidTitle = sidTitle; args.hrasconn = hrasconn; args.pEntry = pEntry; args.pRdp = pRdp; nStatus = DialogBoxParam( g_hinstDll, MAKEINTRESOURCE( nDlg ), hwndOwner, ItDlgProc, (LPARAM )&args ); if (nStatus == -1) { TRACE1("TerminalDlg: GLE=%d", GetLastError()); ErrorDlg( hwndOwner, SID_OP_LoadDlg, ERROR_UNKNOWN, NULL ); nStatus = FALSE; } return (nStatus) ? TRUE : FALSE; } INT_PTR CALLBACK ItDlgProc( IN HWND hwnd, IN UINT unMsg, IN WPARAM wparam, IN LPARAM lparam ) // DialogProc callback for the Interactive Terminal dialog. Parameters // and return value are as described for standard windows 'DialogProc's. // { #if 0 TRACE4( "ItDlgProc(h=$%x,m=$%x,w=$%x,l=$%x)", (DWORD )hwnd, (DWORD )unMsg, (DWORD )wparam, (DWORD )lparam ); #endif switch (unMsg) { case WM_INITDIALOG: { return ItInit( hwnd, (ITARGS* )lparam ); } case WM_HELP: case WM_CONTEXTMENU: { ContextHelp( g_adwItHelp, hwnd, unMsg, wparam, lparam ); break; } case WM_COMMAND: { ITINFO* pInfo = (ITINFO* )GetWindowLongPtr( hwnd, DWLP_USER ); ASSERT(pInfo); return ItCommand( pInfo, HIWORD( wparam ), LOWORD( wparam ), (HWND )lparam ); } case WM_RASAPICOMPLETE: { ITINFO* pInfo = (ITINFO* )GetWindowLongPtr( hwnd, DWLP_USER ); ASSERT(pInfo); // The notification code from the scripting-thread is in 'lparam' // switch (lparam) { case SCRIPTCODE_Done: { EndDialog(hwnd, TRUE); return TRUE; } case SCRIPTCODE_Halted: { MSGARGS msg; // The script has halted programmatically, for instance // because of an explicit "halt" command. Show a popup // indicating things have stopped, but don't dismiss the // dialog. // ZeroMemory(&msg, sizeof(msg)); msg.dwFlags = MB_OK | MB_ICONINFORMATION; MsgDlg( hwnd, SID_OP_ScriptHalted, &msg ); return TRUE; } case SCRIPTCODE_HaltedOnError: { MSGARGS msg; INT nResponse; // There was an execution-error in the script; show a // popup asking if the user wants to view the errors, and // if the user clicks 'Yes' invoke Notepad on the file // %windir%\system32\ras\script.log. Since this is an // error condition, dismiss the dialog. // ZeroMemory(&msg, sizeof(msg)); msg.dwFlags = MB_YESNO | MB_ICONQUESTION; nResponse = MsgDlg( hwnd, SID_OP_ScriptHaltedOnError, &msg ); if (nResponse == IDYES) { ItViewScriptLog( hwnd ); } EndDialog(hwnd, FALSE); return TRUE; } case SCRIPTCODE_KeyboardEnable: { // Allow keyboard input in the edit-box. // EnableWindow(pInfo->hwndEbScreen, TRUE); return TRUE; } case SCRIPTCODE_KeyboardDisable: { // Disallow keyboard input in the edit-box; if the // edit-box currently has the focus, we first set the // focus to the 'Done' button. // if (GetFocus() == pInfo->hwndEbScreen) { SetFocus( GetDlgItem (hwnd, IDOK ) ); } EnableWindow( pInfo->hwndEbScreen, FALSE ); return TRUE; } case SCRIPTCODE_IpAddressSet: { DWORD dwErr; CHAR szAddress[ RAS_MaxIpAddress + 1 ]; // The script is notifying us that the IP address has been // changed programmatically. // // Get the new IP address. // dwErr = RasScriptGetIpAddress( pInfo->hscript, szAddress ); if (dwErr == NO_ERROR) { TCHAR* psz; // Save the new IP address. // psz = StrDupTFromA(szAddress); if (NULL != psz) { // Whistler bug 224074 use only lstrcpyn's to // prevent maliciousness // lstrcpyn( pInfo->pArgs->pszIpAddress, psz, TERM_IpAddress); Free0(psz); } // Display it in the IP-address edit-box // if (pInfo->hwndCcIpAddress) { SetWindowText( pInfo->hwndCcIpAddress, pInfo->pArgs->pszIpAddress ); } } return TRUE; } case SCRIPTCODE_InputNotify: { // Handle input-notification. // return ItRasApiComplete( pInfo ); } return TRUE; } } case WM_DESTROY: { ItTerm( hwnd ); break; } } return FALSE; } BOOL ItCommand( IN ITINFO* pInfo, IN WORD wNotification, IN WORD wId, IN HWND hwndCtrl ) // Called on WM_COMMAND. 'PInfo' is the dialog context. 'WNotification' // is the notification code of the command. 'wId' is the control/menu // identifier of the command. 'HwndCtrl' is the control window handle of // the command. // // Returns true if processed message, false otherwise. // { TRACE3( "ItCommand(n=%d,i=%d,c=$%x)", (DWORD )wNotification, (DWORD )wId, (ULONG_PTR )hwndCtrl ); switch (wId) { case CID_IT_EB_Screen: { // Turn off the default button whenever the terminal window // has the focus. Pressing [Return] in the terminal acts like // a normal terminal. // Button_MakeDefault( pInfo->hwndDlg, pInfo->hwndPbBogus ); // Don't select the entire string on entry. // Edit_SetSel( pInfo->hwndEbScreen, (UINT )-1, 0 ); break; } case IDOK: { TRACE("OK pressed"); if (pInfo->pArgs->pszIpAddress) { GetWindowText( pInfo->hwndCcIpAddress, pInfo->pArgs->pszIpAddress, 16 ); } EndDialog( pInfo->hwndDlg, TRUE ); return TRUE; } case IDCANCEL: TRACE("Cancel pressed"); EndDialog( pInfo->hwndDlg, FALSE ); return TRUE; } return FALSE; } LRESULT APIENTRY ItEbScreenWndProc( HWND hwnd, UINT unMsg, WPARAM wParam, LPARAM lParam ) // Subclassed terminal edit box window procedure. // // Return value depends on message type. // { ITINFO* pInfo; BOOL fSend; BOOL fSendTab; fSend = fSendTab = FALSE; if (unMsg == WM_EOLFROMDEVICE) { // An end-of-line in the device input was received. Send a linefeed // character to the window. // wParam = '\n'; unMsg = WM_CHAR; } else { BOOL fCtrlKeyDown = (GetKeyState( VK_CONTROL ) < 0); BOOL fShiftKeyDown = (GetKeyState( VK_SHIFT ) < 0); if (unMsg == WM_KEYDOWN) { // The key was pressed by the user. // if (wParam == VK_RETURN && !fCtrlKeyDown && !fShiftKeyDown) { // Enter key pressed without Shift or Ctrl is discarded. This // prevents Enter from being interpreted as "press default // button" when pressed in the edit box. // return 0; } if (fCtrlKeyDown && wParam == VK_TAB) { fSend = TRUE; fSendTab = TRUE; } } else if (unMsg == WM_CHAR) { // The character was typed by the user. // if (wParam == VK_TAB) { // Ignore tabs...Windows sends this message when Tab (leave // field) is pressed but not when Ctrl+Tab (insert a TAB // character) is pressed...weird. // return 0; } fSend = TRUE; } } pInfo = (ITINFO* )GetWindowLongPtr( GetParent( hwnd ), DWLP_USER ); ASSERT(pInfo); if (fSend) { DWORD dwErr; pInfo->pbyteSendBuf[ 0 ] = (BYTE )wParam; dwErr = RasScriptSend( pInfo->hscript, pInfo->pbyteSendBuf, SIZE_SendBuf); if (dwErr != 0) { ErrorDlg( pInfo->hwndDlg, SID_OP_RasPortSend, dwErr, NULL ); } if (!fSendTab) { return 0; } } // Call the previous window procedure for everything else. // return CallWindowProc( pInfo->pOldEbScreenWndProc, hwnd, unMsg, wParam, lParam ); } BOOL ItInit( IN HWND hwndDlg, IN ITARGS* pArgs ) // Called on WM_INITDIALOG. 'hwndDlg' is the handle of the phonebook // dialog window. 'pEntry' is caller's entry as passed to the stub API. // // Return false if focus was set, true otherwise, i.e. as defined for // WM_INITDIALOG. // { DWORD dwErr; ITINFO* pInfo; WORD wReceiveSize; WORD wSendSize; WORD wSize; DWORD dwThreadId; TRACE( "ItInit" ); // Allocate the dialog context block. Initialize minimally for proper // cleanup, then attach to the dialog window. // { pInfo = Malloc( sizeof(*pInfo) ); if (!pInfo) { ErrorDlg( hwndDlg, SID_OP_LoadDlg, ERROR_NOT_ENOUGH_MEMORY, NULL ); EndDialog( hwndDlg, FALSE ); return TRUE; } ZeroMemory( pInfo, sizeof(*pInfo) ); pInfo->pArgs = pArgs; pInfo->hwndDlg = hwndDlg; SetWindowLongPtr( hwndDlg, DWLP_USER, (ULONG_PTR )pInfo ); TRACE( "Context set" ); } pInfo->hwndEbScreen = GetDlgItem( hwndDlg, CID_IT_EB_Screen ); ASSERT( pInfo->hwndEbScreen ); pInfo->hwndPbBogus = GetDlgItem( hwndDlg, CID_IT_PB_BogusButton ); ASSERT( pInfo->hwndPbBogus ); if (pArgs->pszIpAddress && pArgs->pszIpAddress[0]) { pInfo->hwndCcIpAddress = GetDlgItem( hwndDlg, CID_IT_CC_IpAddress ); ASSERT( pInfo->hwndCcIpAddress ); if (*pArgs->pszIpAddress) { SetWindowText( pInfo->hwndCcIpAddress, pArgs->pszIpAddress ); } else { SetWindowText( pInfo->hwndCcIpAddress, TEXT("0.0.0.0") ); } } // Set the dialog title. // { TCHAR* psz = PszFromId( g_hinstDll, pArgs->sidTitle ); if (psz) { SetWindowText( hwndDlg, psz ); Free( psz ); } } // Subclass the dialog and screen edit box. // pInfo->pOldWndProc = (WNDPROC )SetWindowLongPtr( pInfo->hwndDlg, GWLP_WNDPROC, (ULONG_PTR )ItWndProc ); pInfo->pOldEbScreenWndProc = (WNDPROC )SetWindowLongPtr( pInfo->hwndEbScreen, GWLP_WNDPROC, (ULONG_PTR )ItEbScreenWndProc ); // Prepare for special TTY-ish painting. // pInfo->hfontEbScreen = SetFont( pInfo->hwndEbScreen, TEXT("Courier New"), FIXED_PITCH | FF_MODERN, 9, FALSE, FALSE, FALSE, FALSE ); pInfo->hbrEbScreen = (HBRUSH )GetStockObject( BLACK_BRUSH ); // Initialize script-processing/data-receipt // { CHAR* pszUserName; CHAR* pszPassword; pszUserName = StrDupAFromT( pInfo->pArgs->pRdp->szUserName ); // Whistler bug 254385 encode password when not being used // Assumed password was encoded by DpInteractive() -or- DwTerminalDlg() // DecodePassword( pInfo->pArgs->pRdp->szPassword ); pszPassword = StrDupAFromT( pInfo->pArgs->pRdp->szPassword ); EncodePassword( pInfo->pArgs->pRdp->szPassword ); // Initialize the script. The script DLL is 'delayload' hence the // exception handling. // __try { dwErr = RasScriptInit( pInfo->pArgs->hrasconn, pInfo->pArgs->pEntry, pszUserName, pszPassword, RASSCRIPT_NotifyOnInput | RASSCRIPT_HwndNotify, (HANDLE)hwndDlg, &pInfo->hscript ); TRACE1( "RasScriptInit(e=%d)", dwErr ); } __except( EXCEPTION_EXECUTE_HANDLER ) { ErrorDlg( hwndDlg, SID_OP_LoadDlg, STATUS_PROCEDURE_NOT_FOUND, NULL ); EndDialog( hwndDlg, FALSE ); return TRUE; } Free0( pszUserName ); // Whistler bug 254385 encode password when not being used // Whistler bug 275526 NetVBL BVT Break: Routing BVT broken // if (pszPassword) { ZeroMemory( pszPassword, strlen(pszPassword) + 1 ); Free( pszPassword ); } // See whether anything went wrong in the script-initialization // if (dwErr == ERROR_SCRIPT_SYNTAX) { MSGARGS msg; INT nResponse; // There was a syntax error in the script; show a popup asking if // the user wants to view the errors, and if so bring up Notepad // on %windir%\system32\ras\script.log. // // Center the dialog on our parent rather than on the dialog, // since the dialog is not yet visible. // ZeroMemory(&msg, sizeof(msg)); msg.dwFlags = MB_YESNO | MB_ICONQUESTION; nResponse = MsgDlg( GetParent( hwndDlg ), SID_ConfirmViewScriptLog, &msg ); if (nResponse == IDYES) { ItViewScriptLog( hwndDlg ); } // Terminate the dialog. This hangs up the connection. // EndDialog( hwndDlg, FALSE ); return TRUE; } else if (dwErr != 0) { ErrorDlg( hwndDlg, SID_OP_LoadDlg, dwErr, NULL ); EndDialog( hwndDlg, FALSE ); return TRUE; } } // Center dialog on the owner window, and hide the owner window which is // currently assumed to be the dial progress dialog. // CenterWindow( hwndDlg, GetParent( hwndDlg ) ); SetOffDesktop( GetParent( hwndDlg ), SOD_MoveOff, NULL ); // Add context help button to title bar. // AddContextHelpButton( hwndDlg ); // Set initial focus to the screen. // SetFocus( pInfo->hwndEbScreen ); return FALSE; } BOOL ItRasApiComplete( IN ITINFO* pInfo ) // Called on WM_RASAPICOMPLETE, i.e. an asynchronous RasPortReceive // completed. 'PInfo' is the dialog context block. // // Returns true if processed the message, false otherwise. // { DWORD dwErr; DWORD dwSize = SIZE_ReceiveBuf; RASMAN_INFO info; TRACE( "RasScriptReceive" ); dwErr = RasScriptReceive( pInfo->hscript, pInfo->pbyteReceiveBuf, &dwSize); TRACE1( "RasScriptReceive=%d",dwErr ); if (dwErr != 0) { ErrorDlg( pInfo->hwndDlg, SID_OP_RasGetInfo, dwErr, NULL ); EndDialog( pInfo->hwndDlg, FALSE ); return TRUE; } info.RI_BytesReceived = (WORD )dwSize; // Send the device talk to the terminal edit box. // if (info.RI_BytesReceived > 0) { CHAR szBuf[ SIZE_ReceiveBuf + 1 ]; CHAR* pch = szBuf; WORD i; TRACE1( "Read %d", info.RI_BytesReceived ); for (i = 0; i < info.RI_BytesReceived; ++i) { CHAR ch = pInfo->pbyteReceiveBuf[ i ]; // Formatting: Converts CRs to LFs (there seems to be no VK_ for // LF) and throws away LFs. This prevents the user from exiting // the dialog when they press Enter (CR) in the terminal screen. // LF looks like CRLF in the edit box. Also, throw away TABs // because otherwise they change focus to the next control. // if (ch == VK_RETURN) { // Must send whenever end-of-line is encountered because // EM_REPLACESEL doesn't handle VK_RETURN characters well // (prints garbage). // *pch = '\0'; // Turn off current selection, if any, and replace the null // selection with the current buffer. This has the effect of // adding the buffer at the caret. Finally, send the EOL to // the window which (unlike EM_REPLACESEL) handles it // correctly. // Edit_SetSel( pInfo->hwndEbScreen, (UINT )-1, 0 ); SendMessageA( pInfo->hwndEbScreen, EM_REPLACESEL, (WPARAM )0, (LPARAM )szBuf ); SendMessage( pInfo->hwndEbScreen, WM_EOLFROMDEVICE, 0, 0 ); // Start afresh on the output buffer. // pch = szBuf; continue; } else if (ch == '\n' || ch == VK_TAB) { continue; } *pch++ = ch; } *pch = '\0'; if (pch != szBuf) { // Send the last remnant of the line. // Edit_SetSel( pInfo->hwndEbScreen, (UINT )-1, 0 ); SendMessageA( pInfo->hwndEbScreen, EM_REPLACESEL, (WPARAM )0, (LPARAM )szBuf ); } } return TRUE; } VOID ItTerm( IN HWND hwndDlg ) // Called on WM_DESTROY. 'HwndDlg' is that handle of the dialog window. // { ITINFO* pInfo = (ITINFO* )GetWindowLongPtr( hwndDlg, DWLP_USER ); TRACE( "ItTerm" ); if (pInfo) { // Close RAS script resources // if (pInfo->hscript) { TRACE( "Stop script processing" ); // Shutdown script processing // TRACE( "RasScriptTerm" ); RasScriptTerm( pInfo->hscript ); TRACE( "RasScriptTerm done" ); } // De-activate WndProc hooks. // if (pInfo->pOldEbScreenWndProc) { SetWindowLongPtr( pInfo->hwndEbScreen, GWLP_WNDPROC, (ULONG_PTR )pInfo->pOldEbScreenWndProc ); } if (pInfo->pOldWndProc) { SetWindowLongPtr( pInfo->hwndDlg, GWLP_WNDPROC, (ULONG_PTR )pInfo->pOldWndProc ); } if (pInfo->hfontEbScreen) { DeleteObject( (HGDIOBJ )pInfo->hfontEbScreen ); } SetOffDesktop( GetParent( hwndDlg ), SOD_MoveBackFree, NULL ); Free( pInfo ); } } VOID ItViewScriptLog( IN HWND hwndOwner ) // Starts notepad.exe on the script log file, script.log. 'HwndOwner' is // the window to center any error popup on. // { DWORD dwSize; TCHAR szCmd[ (MAX_PATH * 2) + 50 + 1 ]; TCHAR* pszCmd; STARTUPINFO si; PROCESS_INFORMATION pi; BOOL f; // Format the command-line string invoking Notepad on the script-log; note // the double-quotes around the script-log's path, which are needed since // RASSCRIPT_LOG is %windir%\system32\ras\script.log and so the expanded // result may contain spaces. // wsprintf( szCmd, TEXT("notepad.exe \"%s\""), TEXT(RASSCRIPT_LOG) ); // Get the size of the expanded command-line // dwSize = ExpandEnvironmentStrings(szCmd, NULL, 0); // Allocate enough space for the expanded command-line // pszCmd = Malloc( (dwSize + 1) * sizeof(TCHAR) ); if (!pszCmd) { ErrorDlg( hwndOwner, SID_OP_LoadScriptLog, GetLastError(), NULL ); return; } // Expand the command-line into the allocated space // ExpandEnvironmentStrings(szCmd, pszCmd, dwSize); // Initialize the startup-info structure // ZeroMemory( &si, sizeof(si) ); si.cb = sizeof(si); // Launch Notepad on the script-log. // f = CreateProcess( NULL, pszCmd, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi ); Free(pszCmd); if (f) { CloseHandle( pi.hThread ); CloseHandle( pi.hProcess ); } else { ErrorDlg( hwndOwner, SID_OP_LoadScriptLog, GetLastError(), NULL ); } } LRESULT APIENTRY ItWndProc( HWND hwnd, UINT unMsg, WPARAM wParam, LPARAM lParam ) // Subclassed dialog window procedure. // // Return value depends on message type. // { ITINFO* pInfo = (ITINFO* )GetWindowLongPtr( hwnd, DWLP_USER ); ASSERT(pInfo); #if 0 TRACE4( "ItWndProc(h=$%x,m=$%x,w=$%x,l=$%x)", (DWORD )hwnd, (DWORD )unMsg, (DWORD )wparam, (DWORD )lparam ); #endif switch (unMsg) { case WM_CTLCOLOREDIT: { // Set terminal screen colors to TTY-ish green on black. // if (pInfo->hbrEbScreen) { SetBkColor( (HDC )wParam, RGB( 0, 0, 0 ) ); SetTextColor( (HDC )wParam, RGB( 2, 208, 44 ) ); return (LRESULT )pInfo->hbrEbScreen; } break; } } // Call the previous window procedure for everything else. // return CallWindowProc( pInfo->pOldWndProc, hwnd, unMsg, wParam, lParam ); }