• Support
  • Articles
  • Resources
  • Products

Tera Term Source Code Overview

12. SSH Design and Implementation in TTSSH

Overview

Original TTSSH plugin for Tera Term was developed by Robert O'Callahan (currently works as Mozilla hacker). It was supporting SSH1, packet compression and port forwarding. However, it did not support SSH2, SCP or SFTP. Development of the original Tera Term stopped in 1998 and its maintenance ended in 2001.

In 2004 newly formed Tera Term Project team resumed development of TTSSH and added SSH2 support to TTSSH. Over the next 3 year period the team implemented full support of SSH2 and SCP. Tera Term Project team is planning to eventually add SFTP support.

Initial TTSSH design was based on OpenSSH code. However, the main differences were that OpenSSH was written for UNIX command line interface, while TTSSH should work under Microsoft Windows and also communicate with Tera Term. As TTSSH developed, the amount of the differences with OpenSSH was increasing and currently the code of these two programs has very significant differences.

SSH Protocol

SSH (Secure Shell) protocols version 1 (version 1.5 to be exact) and version 2 are often referred by short names "SSH1" and "SSH2". These two protocol versions are incompatible with each other. Because of higher security risks associated with SSH1, it is currently barely used.

SSH2 protocol specification is defined in the following Requests for Comment (RFC).


Establishing Connection

Since TTSSH is add-on to Tera Term, it needs to exchange information with Tera Term during establishing, maintaining and closing network connection. For those who are not familiar with SSH communication protocol, this may further complicate understanding of the flow. The drawing below demonstrates the flow that takes place while establishing connection to a remote host.

Establishing SSH Connection

Transmitting Packets

The following code is used when packet is sent to the remote server via SSH2 protocol. Function begin_send_packet() has the return value "pvar-> ssh_state.outbuf + 12", which represents the payload. Payload is pure data; it does not contain packet size, padding, or any other information.

  1. buffer_t *msg;
  2. int len;
  3. char *s;
  4. unsigned char *outmsg;
  5.  
  6. msg = buffer_init();
  7. if (msg != NULL) {
  8. buffer_put_int(msg, SSH2_DISCONNECT_PROTOCOL_ERROR);
  9. s = "disconnected by server request";
  10. buffer_put_string(msg, s, strlen(s));
  11. s = "";
  12. buffer_put_string(msg, s, strlen(s));
  13.  
  14. len = buffer_len(msg);
  15. outmsg = begin_send_packet(pvar, SSH2_MSG_DISCONNECT, len);
  16. memcpy(outmsg, buffer_ptr(msg), len);
  17. finish_send_packet(pvar);
  18. buffer_free(msg);
  19. }

Once ready, SSH packet is sent by finish_send_packet_special() function, which is called from finish_send_packet(). Format of transmitted packet is shows below. Before encrypting the packet using symmetric key, we must create header and footer.

Packet size is the length of Payload and optional Padding data. It does not include optional 20-byte-long HMAC (keyed-Hashing for Message Authentication) field. Packet size is stored in 4 bytes in big-endian format. These 4 bytes are not included in the size value.

While using symmetric key based encryption, payload size must be equal to "block size", otherwise encryption algorithm will not work. Block size depends on encryption algorithm. For example, for 3DES-CBC block size is 24 bytes (192 bits), for AES128 it is 16 bytes (128 bits). If payload size is less than the block size, the end of payload has to be padded to reach the block size.

HMAC creates a hash value for encrypted data. Often used hash algorithm examples are "MD5" and "SHA-1". Adding HMAC to the packet allows to detect "falsification of data by a third party." HMAC generates encrypted text by using cryptographic hash function in combination with a secret cryptographic key and a sequence number. Adding a secret key and a sequence number makes it theoretically impossible for a third party to generate HMAC even if the actual payload was altered.

SSH2 Packet Format (without compression)

Format of the packet compressed with zlib library is shown below. Payload portion of the packet is the only part that is being compressed; other parts remain unchanged. In some cases compressed payload may not reduce the size of the original since compression involves buffering.

When it comes to packet compression, the timing becomes critical. As soon as a client starts SSH connection to a remote server, both hosts begin negotiation process during which will be determined when, or if the packets can be compressed. If mistake is made during this process communication with remote server will not be established.

If normal packet compression is used, compression starts after receiving "SSH2_MSG_KEXINIT". In case of delayed compression, it starts after successful user authentication, i.e. after receiving "SSH2_MSG_USERAUTH_SUCCESS". So, the later method delays data compression after receiving "SSH2_MSG_KEXINIT" until user authentication is completed. Delayed packet compression is more secure because it doesn't expose vulnerability of zlib library. If connection was rerouted to an illegal server delayed compression mechanism may protect the client side.

SSH2 Packet Format (with compression)

Receiving Packets

The function recv() responsible for receiving packets is part of Tera Term core and is unaware whether the packet was sent via TELNET or SSH protocol. Additionally, buffer size is not always specified, which further complicates the implementation.

Receiving SSH2 Packet

Tera Term core contains idle loop OnIdle()#teraterm.cpp that constantly checks if new packets have arrived and need to be processed. CommReceive() function calls recv(). TTSSH adds the hook and replaces socket function recv() with its own function TTXrecv()#ttxssh.c

When CommReceive() calls recv() and passes free pointer and the size of the buffer (cv->InBuff[]) as the arguments. Buffer size is 1KB, which means buffer size value received by TTXrecv() will be in the range 1-1024 bytes.

Then, when PKT_recv() is called by TTXrecv (), the processing becomes a bit more complicated. Below sequence shows handling of the packets when SSH connection is being established.

  1. Call the original recv() in recv_data() to get the packet received from the server by kernel. Update the value of par->pkt_state.datalen.
  2. Perform SSH server version check by using SSH_handle_server_ID(). Update the values of pvar->pkt_state.datastart and pvar->pkt_state.datalen.
  3. Call recv_data() again and since there was no data received from the server, exit the while loop and set connection_closed = TRUE.
  4. Function recv() will return 0 (zero) as no data was received.

The sequence below shows the next steps up-to a symmetric key generation for SSH connectivity.

  1. Call the original recv() in recv_data() to get the packet received from the server by kernel. Update the value of par->pkt_state.datalen.
  2. In SSH_predecrpyt_packet() we want to decrypt only the first block of the received packet. Get the size of the SSH packet.
  3. If packet size is valid, call SSH_handle_packet() to handle the packet according to its type. If will set pvar->ssh_state.payload and pvar->ssh_state.payloadlen.
  4. Update pvar->pkt_state.datastart and pvar->pkt_state.datalen.
  5. Keep calling SSH_predecrpyt_packet() until pvar->pkt_state.datalen reaches 0 (zero).
  6. Call recv_data() again and since there was no data received from the server, exit the while loop and set connection_closed = TRUE.
  7. Function recv() will return 0 (zero) as no new data was received.

The sequence below shows data exchange with the terminal.

  1. Call the original recv() in recv_data() to get the packet received from the server by kernel. Update the value of par->pkt_state.datalen.
  2. In SSH_predecrpyt_packet() we want to decrypt only the first block of the received packet. Get the size of the SSH packet.
  3. If packet size is valid, call SSH_handle_packet() to handle the packet according to its type. If will set pvar->ssh_state.payload and pvar->ssh_state.payloadlen.
  4. The message type is SSH2_MSG_CHANNEL_DATA, so call the function handle_SSH2_channel_data(). It will sets the pvar->ssh_state.payload_datalen and pvar-> ssh_state.payload_datastart.
  5. Update pvar->pkt_state.datastart and pvar->pkt_state.datalen.
  6. If SSH_is_any_payload() returns TRUE, copy the data to the buffer that was passed to PKT_recv().
  7. If TeraTerm side buffer becomes full, even if there is more SSH terminal data, return PKT_recv().
  8. If TeraTerm side buffer is not full, call recv_data() and get more data from the server.
  9. Tera Term core recv() function returns "received data size".

Sequence Control

SSH protocol allows to encrypt client-server communication by using encryption key. Public-key based encryption (asymmetric) provides higher level of security, however it is more resource intensive and not used with SSH. SSH2 utilizes symmetric encryption algorithms with shared key - AES (Advanced Encryption Standard: Rijndael algorithm) and 3DES (Triple Data Encryption Standard).

The key is securely shared between two parties establishing connection and is unknown to a third party. As per SSH2 protocol, when client opens TCP connection to a remote host (SSH server), unique DH key is generated based on "Diffie-Hellman" algorithm. This key is only known to the client and to the server.

Prior to creation of DH key, network packets are transmitted unencrypted (as a clear text) and can be captured by a third party, however DH algorithm allows to establish a shared secret between two parties in such a way that it cannot be compromised.

Once shared key has been generated, it can be used to encrypt and decrypt the packets. SSH2 protocol assigns each packet the Message Number in the range 1 to 255. Message number describes the payload of the packet. RFC4250 contains the list of all supported message numbers. SSH messages also have names that start with "SSH2_MSG_". They are defined as macros in TTSSH source code.

The drawing below shows the packet flow for TCP connection from a client to a server on the default SSH port 22 with password based user authentication.

SSH2 Connection Setup Sequence

The next drawing shows the flow of the packets when client explicitly closes the connection, i.e. enters "exit" or "logout" command in the remote shell.

SSH2 Connection Close Sequence

In addition to password based SSH authentication, TTSSH also supports keyboard-interactive, publickey based and publickey with Pageant authentication methods. Packet flows for each of these methods are shown below.

SSH Authentication

SSH Authentication (Pageant)
Pseudo-Terminal

SSH2 contains "flow control" mechanism. It is similar to TCP windowing and is implemented by introducing "windows size" concept. Flow control allows to avoid buffer overflows during communication between the client (Tera Term) and the server (SSH daemon).

Despite the flow control, pasting large amounts of data from Clipboard into Tera Term window may cause server to drop part of the data. Knowledge of UNIX Pseudo-Terminal (PTY) in required to understand this behavior.

SSH daemon on the server (sshd) needs to show to the client that it is directly connected to the shell. From another side shell program does not care if the request to print a character, or read a character came from legacy serial console, VGA terminal, or SSH connection; shell just calls corresponding functions printf(3), or scanf(3) from C library.

When sshd receives connection request from a client, it calls openpty(3) function to initialize pseudo-terminal. In order to connect a client with a server, pseudo-terminal provides "master device" and "slave device" kernel drivers. Device file representing master device is "/dev/pty[p-za-e][0-9a-f] ". Device file representing the slave device is "/dev/tty[p-za-e][0-9a-f] ". In other words to reach the shell, sshd needs to access master device. Shell process in being forked by sshd and becomes its child process. Shell then communicates with already initialized slave device driver.

The mechanism of pseudo-terminal allowing to connected sshd with shell is shown below.

Pseudo-Terminal

Terminal line discipline is the module allowing to perform "inline editing" during command input, i.e. the program receives data via getchar() function, however the processing won't start until user hits Enter key. Linux systems support 16 different line disciplines that can be found in the file /proc/tty/ldiscs. N_TTY is used as the default discipline.

SCP (Secure Copy)

SCP is one of the programs included in OpenSSH package. It allows to transmit and receive files within SSH session. In order to use the secure copy, in addition to "sshd" the server should support "scp" command. In OpenSSH implementation "scp" is started by sshd daemon as a child process. It should be noted that SCP and SFTP (Secure File Transfer Protocol) are completely different protocols incompatible with each other. SCP can perform only file "receive" or "send" operations.

To transfer files within SSH session, after establishing successful connection between the client and the server, the function responsible for executing external commands (exec) must call "scp".

  • SCP via SSH2

When you send SSH2_MSG_CHANNEL_REQUEST message to the server, you can run an external command by specifying the service name "exec" instead of "pty-req".

After successful user authentication
----> SSH2_MSG_CHANNEL_OPEN (90)
<---- SSH2_MSG_CHANNEL_OPEN_CONFIRMATION (91)
----> SSH2_MSG_CHANNEL_REQUEST (98) external command sent in the service name "exec" ("scp -f")
<---- SSH2_MSG_CHANNEL_WINDOW_ADJUST (remote_window + = 131072 bytes)
<---- SSH2_MSG_CHANNEL_EXTENDED_DATA (local_window- = 36 bytes)
<---- SSH2_MSG_CHANNEL_DATA (94)

  • SCP via SSH1

Sending SSH_CMSG_EXEC_CMD to the server allows to run an external command during SSH1 session.

External command has the following format:

scp [-v] [-r] [-p] [-d] -t file name  ;copy Local-to-Remote
scp [-v] [-r] [-p] [-d] -f file name  ;copy Remote-to-Local
  -v  verbose
  -r recursive
  -p keep time stamp
  -d directory
  -t copy Local-to-Remote
  -f copy Remote-to-Local

  • Data Transfer

As soon as transmission of external command has been completed, we can start sending or receiving the file content.

  1. File Sending Flow
    <---- Send timestamp (optional)
    <---- Send "C0664 file_size filename" ; 664 in this example is file permission at destination
    <---- Send content of the file
    <---> Close session
  2. File Receiving Flow
    ----> Receive timestamp (optional)
    <---- Send 0
    ----> Receive "C0664 file_size filename"
    <---- Send 0
    ----> Receive content of the file
    ----- Set file timestamp (optional)
    <---- Send 0
    <---> Close session

  • Note

When file name contains full path, forward slash ("/") must be used as directory separator. Backslashes ("\") are not supported and should be replaced with forward slashes.

X11 Forwarding

X11 forwarding (X11 port forwarding) allows to launch X Window applications on SSH server and bring application GUI to the local PC, where Tera Term is running. By using this mechanism user can run remote applications such as "xeyes", "firefox" or "xemacs" through SSH session. Note, that X server application, such as Xming, must be running on local PC before SSH session with X11 forwarding is established.

The drawing below shows the packet flow during X11 forwarding. As it can be seen, Tera Term (TTSSH) acts as a bridge between X application and X server. Tera Term's "redirector" or "port forwarder" is called "TCP Proxy".

X11 Forwarding

In order to use X11 forwarding, certain configuration changes have to be done on both - Tera Term and SSH server.

On Tera Term side the following value needs to be set in teraterm.ini file.

    [TTSSH]
    DefaultForwarding = X

On the server side, in case of using OpenSSH, X11Forwarding value needs to be set to "yes" in "sshd_config" file. Default value is "no", which disables support of X11 forwarding.

    X11Forwarding = yes

When X11 forwarding is enabled, Tera Term sets request type to "FWD_REMOTE_X11_TO_LOCAL". This means the forwarding will be performed from SSH server towards Tera Term. After opening the session, Tera Term sends "SSH2_MSG_CHANNEL_OPEN_CONFIRMATION" message to the remote host to initialize X11 transfer.

    if (c->type == TYPE_SHELL) {
        // Preparing port forwarding (2005.2.26, 2005.6.21 yutaka)
        // X11 request must be issued after opening the shell (2005.7.3 yutaka)
        FWD_prep_forwarding(pvar);
        FWD_enter_interactive_mode(pvar);
    }

FWD_prep_forwarding() function sends "x11-req" service name and "MIT-MAGIC-COOKIE-1" to SSH server. This initializes X11 forwarding on the server. After completing initialization of X11 server will automatically set environment variable "DISPLAY".

    # echo $ DISPLAY
    DISPLAY = localhost: 10.0  

When local PC is ready, user can activate X application on SSH server. The server sends X application data to Tera Term in SSH2_MSG_CHANNEL_DATA message format. This data is processed by FWD_received_data() function, which then forwards it to X server (TCP/6000). X server will receive the data in channel->local_socket and will treat it in non-blocking mode. Furthermore, since not all packets may be sent at once, prior to being processed further, the data must be accumulated in an internal buffer. Once channel->local_socket receives the data it sends out FD_WRITE message and calls write_local_connection_buffer() function. If there is a data in the buffer that has not been sent the last time, it will be read from the buffer and another attempt will be made to send it.

When user performs an operation in X11 screen, X server needs to send data via Tera Term to SSH server. In this case FD_READ message is generated by Tera Term and read_local_connection() function is called. Then Tera Term puts the data received form X server into SSH2_MSG_CHANNEL_DATA message format and forwards it to SSH server.


Copyright © 2008-2015 Tera Term Project