Windows users who need a command line connection to another system via telnet or SSH are big fans of PuTTY. It's free, it has every feature you need, and it's reliable.

One thing many people would like to do is use PuTTY as a component in their program. Apparently this comes up so often enough that there is a FAQ entry dedicated to the topic. Alas, PuTTY does not have any sort of automation interface, so this goal has always been out of reach.

In this article I will show you how to work around this minor shortcoming. Creating a version of PuTTY that can be driven from a Windows program turns out to be an easy task. I'll demonstrate this with a small C++ program that shows exactly how to get this versatile program to do your bidding. My solution works for C++, but the changes I make should work well with any Windows software that can properly process a few messages.

Putting Together the Project

I'm using Visual Studio 2010 to build both my program and the modified version of Putty. I created the basic outline as follows:

  1. Use the File|New|Project menu item to bring up the list of available project wizards.
  2. Select MFC project, and enter a project name (I used the uninspired name PuttyDriver.)
  3. I don't want the default MFC settings, so in the MFC App Wizard, select the Next button.
  4. On the Application Type page of the wizard, change the Application Type to Dialog Based.
  5. The project is ready to go at this point, you can click the Finish button and then build your initial project.

My driver program is only going to do one thing: direct putty to connect to the host of my choice, then log in using canned credentials. The resulting UI is shown below, and I am going to leave the very minor details of creating it up to the reader.

The driver program - a simple dialog-based MFC app

Adding Putty to the Project

The next step in this process is to add the Putty components to the project. I downloaded version 0.61 of the PuTTY source from the download page and extracted it to a separate folder. I then used Visual Studio's File|Add|Existing Project to add the compatible project file, Putty.dsp, found in /Windows/MSVC/Putty. Visual Studio has to convert this project to a version 10 project file, but it should do so with no problems.

I then right-clicked on the Putty project in Solution Explorer and renamed it to AutoPutty. Since this version of PuTTY will have some slightly different behavior, I don't want to confuse the executable I am creating with the real thing.

From Project|Project Dependencies, I set the PuttyDriver project to depend on AutoPutty - this insures that both projects get built when I build the entire solution.

My final change to the project is to modify the output directory for both Debug and Release versions of AutoPutty. I set the project to build the executable in the root directory of my PuttyDriver project - this will make it easy to find the executable when I need to launch it. I had to make this change in two places: Properties|Configuration Properties|General|Output Directory and Properties|Linker|Outuput File.

When you finally build the project, you'll find that current version of Microsoft's C++ compiler complain quite a bit about the use of functions like strcpy - Microsoft would like you to use safer replacement functions. You may choose to turn those errors off by defining _CRT_SECURE_NO_WARNINGS in the project file. While you are there, you should define SECURITY_WIN32 as well - it is required by Windows header sspi.h.

After a successful build you should find a copy of AutoPutty.exe in the root directory of your project, and it should run on your system and behave just like PuTTY.

Launching AutoPutty

If I'm going to have a PuTTY component in my PuttyDriver program, one of the first things I need is to be able to start and stop AutoPutty. So my first step in this project is to create the code that launches the program from PuttyDriver. The code below is inserted into the handler for the Start button:

UpdateData( true );
char path[MAX_PATH];
GetCurrentDirectory(MAX_PATH, path);
if ( path[ strlen(path) - 1 ] != '\\' )
    strcat_s( path, MAX_PATH, "\\" );
strcat_s( path, MAX_PATH, "AutoPutty.exe -ssh " );
strcat_s( path, MAX_PATH, m_HostName.GetBuffer() );
PROCESS_INFORMATION pi;
ZeroMemory(&pi, sizeof(pi) );
STARTUPINFO si;
ZeroMemory(&si, sizeof(si) );
si.cb = sizeof(si);
if ( CreateProcess( NULL, path, NULL, NULL, NULL, NULL, NULL, NULL, &si, &pi ) )
{
    Sleep( 1000 );
    BringWindowToTop();
}

This code assumes that AutoPutty.exe is in the current directory, and launches it with a command line telling it to connect to the host named in the dialog using ssh. Assuming that you have the project set up properly, pushing the start button should now start an independent copy of AutoPutty, which will behave identically to classic PuTTY.

Taking Ownership of AutoPutty

At this point I can successfully launch AutoPutty, but I can't really start calling this an integrated part of my main program, PuttyDriver. All I have done is set up a launcher for a separate executable.

The next step in the integration process is to establish PuttyDriver as the owner of AutoPutty's main window. Most Windows programmers are familiar with the traditional parent/child relationship between windows. That relationship is well understood, but I can't use it here - it doesn't work for two top level windows.

Setting PuttyDriver to be the owner (as opposed to the parent) of AutoPutty has the following effects, as explained here by Microsoft:

  • The owned window will always be above its owner in the z-order.
  • The system automatically destroys the owned window when the owner is destroyed.
  • The owned window is hidden when the owner is minimized.

The most straightforward way to set ownership of the window is to pass the owner's handle in the call to CreateWindow(), which means I will now make my first modifications to the PuTTY source code.

There are a number of ways to pass the owner handle to AutoPutty for use in the call to CreateWindow(), with the most obvious being to pass it on the command line. In the interest of minimizing changes to the existing PuTTY code base, I elected to pass it by creating an environment variable that holds the owner window handle. Since a child process inherits the parent's environment, this is a no-fuss way to get the data to AutoPutty.

I added the following code to the end of InitDialog() in PuttyDriver:

CString hwnd_text;
hwnd_text.Format( "%d", m_hWnd );
SetEnvironmentVariable("PUTTY_OWNER", hwnd_text );

This sets the environment variable for AutoPutty to find when it gets launched.

Now I come to the point where I am actually making changes to the PuTTY code. Fortunately, all of the changes needed for this program are confined to two files: terminal.c and windows/window.c. My first change is to window.c. This file contains the WndProc for the PuTTY window, and thus most of the rendering and control code for the GUI.

In order to establish the Owner/Owned relationship, I need to modify the code that calls CreateWindow(). I hoisted the function call into a block, added code to get the owner window handle, and inserted the handle into the call to CreateWindow():

{
    HWND owner_hwnd = 0;
    char buffer[ 132 ];
    if ( GetEnvironmentVariable( "PUTTY_OWNER", buffer, 132 ) ) 	
        sscanf( buffer, "%d", &owner_hwnd );
    if ( owner_hwnd == 0 )
        MessageBox( NULL, 
                    "AutoPutty did not find the handle for the "
                    "owner window, this is not going to work", 
                    "Fail",
                    MB_OK );
    hwnd = CreateWindowEx(exwinmode, appname, appname,
                          winmode, CW_USEDEFAULT, CW_USEDEFAULT,
                          guess_width, guess_height,
                          owner_hwnd, NULL, inst, NULL);
}

At this point I've only modified one small block of code in the PuTTY source, but I'm well on my way to having it behave more like a component of PuttyDriver and less like an independent program. The ownership status means that the two programs only appear once on the taskbar, and will only appear once when you are pressing ALT-TAB to select a new active process. And they only produce a single entry in the Applications Tab of Task Manager.

The Communications Link

In order to achieve the automation that I am seeking, I also need to have two way communications between AutoPutty and the driver program. Since this is Windows, a natural choice for communications is to use native Windows messages. In order to do this, both programs need the Window handle of their opposite number.

I've already solved half of that problem through the ownership relationship established when I created the main window for AutoPutty. Now that it has set PuttyDriver as its owner window, I can get this window handle any place in the program through a simple function call:

HWND parent = GetWindow(hwnd, GW_OWNER);

But the reverse is not true - PuttyDriver does not know have a copy of the window handle for AutoPutty.

To remedy this situation, I added code to window.c that notifies its owner when it s created, and when it is destroyed. First I add this statement immediately after the call to CreateWindow():

if ( owner_hwnd )
   PostMessage( owner_hwnd, WM_APP, 0, (LPARAM) hwnd );

This tells PuttyDriver that the window is created, and gives it the handle to use for communications.

I also need to know when the window is closed, and I have to add that code two places in window.c - because Putty can be shut down two different ways.

Normally AutoPutty will shut down in response to a windows message. When this happens, I can count on a WM_CLOSE message being sent to the Windows Procedure. I add this code the existing handler for WM_CLOSE:

if (!cfg.warn_on_close || session_closed ||
    MessageBox(hwnd,
               "Are you sure you want to close this session?",
               str, MB_ICONWARNING | MB_OKCANCEL | MB_DEFBUTTON1)
    == IDOK) {
    HWND parent = GetWindow(hwnd, GW_OWNER);
    if ( parent )
        SendMessage( parent, WM_APP, 0, 0 );
    DestroyWindow(hwnd);
}

This lets PuttyDriver know that the window has been destroyed.

The original PuTTY code has an alternative method of shutdown. When it receives one of several possible network events, such as a telnet connection being broken, it calls PostQuitMessage(). When a program shuts down this way, it doesn't issue messages to destroys its windows - it relies on the O/S to destroy the windows when the process exists. As a result, I have to make a change in WinMain(), the main window procedure for PuTTY. This procedure extracts the messages sent to it using PeekMessage, and I add some code to handle the processing when a WM_QUIT message is sent:

if (msg.message == WM_QUIT) {
    HWND parent = GetWindow(hwnd, GW_OWNER);
    if ( parent )
        SendMessage( parent, WM_APP, 0, 0 );			
    goto finished;	       /* two-level break */
}

Handling the AutoPutty Lifecycle Events

To keep track of the state of AutoPutty, I have to add a handler for WM_APP to PuttyDriver. It does two things when handling the incoming WM_APP event.

First, then handler stores the handle of the AutoPutty window - or sets the value to 0 when the window has been destroyed.

Second, it either enables or disables the button used to start up AutoPutty. Since this program can only manage one window at a time, I don't want to allow any inadvertent button pushes:

afx_msg LRESULT CPuttyDriverDlg::OnWmApp(WPARAM wParam, LPARAM lParam)
{
    m_PuttyWindow = (HWND) lParam;
    m_StartButton.EnableWindow( !m_PuttyWindow );
    return 0;
}

One final piece of bookkeeping is to make sure that the AutoPutty window is shut down when PuttyDriver shuts down. (The Windows documentation claims this happens automatically to owned windows, but it doesn't seem to be the case.)

void CPuttyDriverDlg::OnDestroy()
{
    CDialogEx::OnDestroy();
    if ( m_PuttyWindow )
        ::SendMessage( m_PuttyWindow, WM_CLOSE, 0, 0 );
}

Monitoring Input Traffic

Now that I have control over the lifetime of my AutoPutty window, it's time to take the next step in automation. My driver program needs to watch all the data coming in from the remote end so that it can take action on various types of input.

Depending on how you set up your connection, PuTTY can receive input data from a serial port, a Telnet connection, or an SSH connection. Fortunately the Windows version of PuTTY uses a standard handle-based interface to all three types of connections. The routine term_data() in terminal.c is called as data arrives, regardless of the source.

Since we are using the Windows API to communicate between processes, it makes sense to use the WM_COPYDATA message to send data to the parent program as it arrives. WM_COPYDATA is a good choice, as it takes care of marshalling the data between the two processes, which can add some complication to other solutions. The modified routine is shown below:

int term_data(Terminal *term, int is_stderr, const char *data, int len)
{
    HWND parent = GetWindow(hwnd, GW_OWNER);
    if ( parent ) {
        COPYDATASTRUCT cd;
        cd.dwData = (ULONG_PTR) 0xDEADBEEF;
        cd.cbData = len;
        cd.lpData = (PVOID) data;
        SendMessage( parent, WM_COPYDATA, (WPARAM) hwnd, (LPARAM) &cd );
    }

Receiving the Data

To receive this messages in PuttyDriver, I simply create a handler for WM_COPYDATA and start grabbing the data as it arrives. One important thing to note is that because AutoPutty has to use SendMessage() to send the data to its parent, it has to wait for PuttyDriver to finish processing the data until it can continue. This dictates a certain style of behavior on my part.

There are quite a few ways to skin this cat, and I'm keeping it very simple here. I'm using a deque<char> container to hold the last 64 characters I've received. After each WM_COPYDATA message I received, I check to see if the current output snapshot ends in one of my trigger messages. If it does, I post the message number to myself for later processing, then return so that AutoPutty can continue its work.

The code I'm using here is doing something fairly simple: automating the login process by using the credentials that I've entered into the dialog box. That means the two strings I'm looking for are the login and password prompts. The resulting code is shown here:

BOOL CPuttyDriverDlg::OnCopyData(CWnd* pWnd, COPYDATASTRUCT* pCopyDataStruct)
{
    char *p = (char *) pCopyDataStruct->lpData;
    int len = pCopyDataStruct->cbData;
    if ( len >= 64 ) {
        p += len - 64;
        len = 64;
        m_Snapshot.clear();
    }
    while ( len-- )
        m_Snapshot.push_front(*p++);
    m_Snapshot.resize(64);
    static const char *needles[2] = { "login as: ", "password: " };
    for ( int i = 0 ; i < 2 ; i++ ) {
        int len = strlen( needles[i] );
        int j;
        for ( j = 0 ; j < len ; j++ ) {
            if ( needles[i][j] != m_Snapshot[len-1-j] )
                break;
        }
        if ( j == len ) 
            PostMessage( WM_APP+1, i, 0 );
    }
    return TRUE;
}

There is plenty of room for improvement in this routine, much of it depending on what type of automation you are going to be using in your program. Some obvious items would include the ability to add and remove triggers as the program progresses, and regular expression matching for triggers.

Driving PuTTY

This login program is now complete save for one detail: I need a way to send my responses back to AutoPutty.

The first part of this is pretty obvious - I just need to read the data from the dialog box and post it to AutoPutty with my useful WM_COPYDATA command. This happens in my WM_APP+1 handler:

afx_msg LRESULT CPuttyDriverDlg::OnWmAppPlusOne(WPARAM wParam, LPARAM lParam)
{
    UpdateData(TRUE);
    CString msg;
    switch ( wParam ) {
    case 0 :
        msg = this->m_UserId + '\r'; break;
    case 1:
        msg = this->m_Password + '\r'; break;
    }
    if ( this->m_PuttyWindow ) {
        COPYDATASTRUCT cd;
        cd.dwData = (ULONG_PTR) 0xF00DFACE;
        cd.cbData = msg.GetLength();
        cd.lpData = (PVOID) (const char *) msg;
        ::SendMessage( this->m_PuttyWindow, 
                       WM_COPYDATA, 
                       (WPARAM) this->m_hWnd, 
                       (LPARAM) &cd );
    }
    return 0;
}

Sending this data to AutoPutty is fine, but right now the program doesn't do anything with that message. The final piece of work is to add a WM_COPYDATA handler to window.c.

Simply grabbing the data is easy enough - the data structure that accompanies the message contains a pointer to the data and a value indicating its length. However, I have two problems I have to solve before the data is actually sent out to the to whatever device AutoPutty is connected to.

First, I have to take into account the fact that PuTTY was written to use wide characters. My driver program was built using MultiByte characters, so we have a mismatch. This means I have to do a conversion of the data from one domain to the other. This is a two step process - I call MultiByteToWideChar() once to determine how much space I need, then I allocate a buffer and call it again.

The second thing I need to do is determine what to do with the data once I've converted it. PuTTY takes all terminal input and eventually passes through a function called luni_send(). Calling this function directly from the Windows procedure seems to work just fine.

The WM_COPYDATA handler I created looks like this:

case WM_COPYDATA :
{
    COPYDATASTRUCT *cd = (COPYDATASTRUCT *) lParam;
    int wsize = MultiByteToWideChar( CP_ACP,
                                     MB_PRECOMPOSED,
                                     (LPCSTR) cd->lpData,
                                     cd->dwData,
                                     NULL,
                                     0 );
    wchar_t *buf = (wchar_t *) calloc( wsize+1, sizeof(wchar_t) );
    MultiByteToWideChar( CP_ACP,
                         MB_PRECOMPOSED,
                         (LPCSTR) cd->lpData,
                         cd->dwData,
                         buf,
                         wsize + 1 );
    if (term->ldisc)
        luni_send(term->ldisc, buf, wsize, 0);
    free( buf );
}

At this point I have a working program - it connects to my designated host, and sends the username and password of my choice to the host, connecting me to the system.

I should add a note of caution here. Automating logins is a tempting time saver, but in general this is a really bad idea. Any time you hard code credentials into a program, you open the door to all sorts of new attacks on your system.

In my demo program, the user has to enter a name and password, so nothing is hardcoded, but even this adds security holes to a system. I encourage you to think of this as a demonstration only.

Demo of the program in action
For a better view, go to full screen and select 720p

Source Code

I've included the complete source code for PuttyDriver, the MFC project that controls AutoPutty. It was built with Visual Studio 2010, so you may have a little work to do if you backport it to earlier versions. My use of language features and classes should be compatible with much earlier versions - this is all very simple code.

Because PuTTY is always changing, I am not redistributing a snapshot of the version I used. Instead, I'm including before and after copies of the two source files I modified: window.c and terminal.c. If you build with Putty 0.61, you should be able to drop these two files right on top of the files included with the distribution and be on your way. With later versions of PuTTY you will have to perform an intelligent merge of the changes, which I hope will be a fairly effortless process.

Downloads

  • PuttyDriver.zip. The PuttyDriver source and project. You will need to add the PuTTY project to this solution as described in the article.
  • putty.zip. This contains the two PuTTY source files modified for this project. Both the original 0.61 source and my modified source are supplied. Executables are supplied as well, which may or may not work on your system.