Blame doc/shell.dox

Packit 6c0a39
/**
Packit 6c0a39
@page libssh_tutor_shell Chapter 3: Opening a remote shell
Packit 6c0a39
@section opening_shell Opening a remote shell
Packit 6c0a39
Packit 6c0a39
We already mentioned that a single SSH connection can be shared
Packit 6c0a39
between several "channels". Channels can be used for different purposes.
Packit 6c0a39
Packit 6c0a39
This chapter shows how to open one of these channels, and how to use it to
Packit 6c0a39
start a command interpreter on a remote computer.
Packit 6c0a39
Packit 6c0a39
Packit 6c0a39
@subsection open_channel Opening and closing a channel
Packit 6c0a39
Packit 6c0a39
The ssh_channel_new() function creates a channel. It returns the channel as
Packit 6c0a39
a variable of type ssh_channel.
Packit 6c0a39
Packit 6c0a39
Once you have this channel, you open a SSH session that uses it with
Packit 6c0a39
ssh_channel_open_session().
Packit 6c0a39
Packit 6c0a39
Once you don't need the channel anymore, you can send an end-of-file
Packit 6c0a39
to it with ssh_channel_close(). At this point, you can destroy the channel
Packit 6c0a39
with ssh_channel_free().
Packit 6c0a39
Packit 6c0a39
The code sample below achieves these tasks:
Packit 6c0a39
Packit 6c0a39
@code
Packit 6c0a39
int shell_session(ssh_session session)
Packit 6c0a39
{
Packit 6c0a39
  ssh_channel channel;
Packit 6c0a39
  int rc;
Packit 6c0a39
Packit 6c0a39
  channel = ssh_channel_new(session);
Packit 6c0a39
  if (channel == NULL)
Packit 6c0a39
    return SSH_ERROR;
Packit 6c0a39
Packit 6c0a39
  rc = ssh_channel_open_session(channel);
Packit 6c0a39
  if (rc != SSH_OK)
Packit 6c0a39
  {
Packit 6c0a39
    ssh_channel_free(channel);
Packit 6c0a39
    return rc;
Packit 6c0a39
  }
Packit 6c0a39
Packit 6c0a39
  ...
Packit 6c0a39
Packit 6c0a39
  ssh_channel_close(channel);
Packit 6c0a39
  ssh_channel_send_eof(channel);
Packit 6c0a39
  ssh_channel_free(channel);
Packit 6c0a39
Packit 6c0a39
  return SSH_OK;
Packit 6c0a39
}
Packit 6c0a39
@endcode
Packit 6c0a39
Packit 6c0a39
Packit 6c0a39
@subsection interactive Interactive and non-interactive sessions
Packit 6c0a39
Packit 6c0a39
A "shell" is a command interpreter. It is said to be "interactive"
Packit 6c0a39
if there is a human user typing the commands, one after the
Packit 6c0a39
other. The contrary, a non-interactive shell, is similar to
Packit 6c0a39
the execution of commands in the background: there is no attached
Packit 6c0a39
terminal.
Packit 6c0a39
Packit 6c0a39
If you plan using an interactive shell, you need to create a
Packit 6c0a39
pseud-terminal on the remote side. A remote terminal is usually referred
Packit 6c0a39
to as a "pty", for "pseudo-teletype". The remote processes won't see the
Packit 6c0a39
difference with a real text-oriented terminal.
Packit 6c0a39
Packit 6c0a39
If needed, you request the pty with the function ssh_channel_request_pty().
Packit 6c0a39
Then you define its dimensions (number of rows and columns)
Packit 6c0a39
with ssh_channel_change_pty_size().
Packit 6c0a39
Packit 6c0a39
Be your session interactive or not, the next step is to request a
Packit 6c0a39
shell with ssh_channel_request_shell().
Packit 6c0a39
Packit 6c0a39
@code
Packit 6c0a39
int interactive_shell_session(ssh_channel channel)
Packit 6c0a39
{
Packit 6c0a39
  int rc;
Packit 6c0a39
Packit 6c0a39
  rc = ssh_channel_request_pty(channel);
Packit 6c0a39
  if (rc != SSH_OK) return rc;
Packit 6c0a39
Packit 6c0a39
  rc = ssh_channel_change_pty_size(channel, 80, 24);
Packit 6c0a39
  if (rc != SSH_OK) return rc;
Packit 6c0a39
Packit 6c0a39
  rc = ssh_channel_request_shell(channel);
Packit 6c0a39
  if (rc != SSH_OK) return rc;
Packit 6c0a39
Packit 6c0a39
  ...
Packit 6c0a39
Packit 6c0a39
  return rc;
Packit 6c0a39
}
Packit 6c0a39
@endcode
Packit 6c0a39
Packit 6c0a39
Packit 6c0a39
@subsection read_data Displaying the data sent by the remote computer
Packit 6c0a39
Packit 6c0a39
In your program, you will usually need to receive all the data "displayed"
Packit 6c0a39
into the remote pty. You will usually analyse, log, or display this data.
Packit 6c0a39
Packit 6c0a39
ssh_channel_read() and ssh_channel_read_nonblocking() are the simplest
Packit 6c0a39
way to read data from a channel. If you only need to read from a single
Packit 6c0a39
channel, they should be enough.
Packit 6c0a39
Packit 6c0a39
The example below shows how to wait for remote data using ssh_channel_read():
Packit 6c0a39
Packit 6c0a39
@code
Packit 6c0a39
int interactive_shell_session(ssh_channel channel)
Packit 6c0a39
{
Packit 6c0a39
  int rc;
Packit 6c0a39
  char buffer[256];
Packit 6c0a39
  int nbytes;
Packit 6c0a39
Packit 6c0a39
  rc = ssh_channel_request_pty(channel);
Packit 6c0a39
  if (rc != SSH_OK) return rc;
Packit 6c0a39
Packit 6c0a39
  rc = ssh_channel_change_pty_size(channel, 80, 24);
Packit 6c0a39
  if (rc != SSH_OK) return rc;
Packit 6c0a39
Packit 6c0a39
  rc = ssh_channel_request_shell(channel);
Packit 6c0a39
  if (rc != SSH_OK) return rc;
Packit 6c0a39
Packit 6c0a39
  while (ssh_channel_is_open(channel) &&
Packit 6c0a39
         !ssh_channel_is_eof(channel))
Packit 6c0a39
  {
Packit 6c0a39
    nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0);
Packit 6c0a39
    if (nbytes < 0)
Packit 6c0a39
      return SSH_ERROR;
Packit 6c0a39
Packit 6c0a39
    if (nbytes > 0)
Packit 6c0a39
      write(1, buffer, nbytes);
Packit 6c0a39
  }
Packit 6c0a39
Packit 6c0a39
  return rc;
Packit 6c0a39
}
Packit 6c0a39
@endcode
Packit 6c0a39
Packit 6c0a39
Unlike ssh_channel_read(), ssh_channel_read_nonblocking() never waits for
Packit 6c0a39
remote data to be ready. It returns immediately.
Packit 6c0a39
Packit 6c0a39
If you plan to use ssh_channel_read_nonblocking() repeatedly in a loop,
Packit 6c0a39
you should use a "passive wait" function like usleep(3) in the same
Packit 6c0a39
loop. Otherwise, your program will consume all the CPU time, and your
Packit 6c0a39
computer might become unresponsive.
Packit 6c0a39
Packit 6c0a39
Packit 6c0a39
@subsection write_data Sending user input to the remote computer
Packit 6c0a39
Packit 6c0a39
User's input is sent to the remote site with ssh_channel_write().
Packit 6c0a39
Packit 6c0a39
The following example shows how to combine a nonblocking read from a SSH
Packit 6c0a39
channel with a nonblocking read from the keyboard. The local input is then
Packit 6c0a39
sent to the remote computer:
Packit 6c0a39
Packit 6c0a39
@code
Packit 6c0a39
/* Under Linux, this function determines whether a key has been pressed.
Packit 6c0a39
   Under Windows, it is a standard function, so you need not redefine it.
Packit 6c0a39
*/
Packit 6c0a39
int kbhit()
Packit 6c0a39
{
Packit 6c0a39
    struct timeval tv = { 0L, 0L };
Packit 6c0a39
    fd_set fds;
Packit 6c0a39
Packit 6c0a39
    FD_ZERO(&fds);
Packit 6c0a39
    FD_SET(0, &fds);
Packit 6c0a39
Packit 6c0a39
    return select(1, &fds, NULL, NULL, &tv;;
Packit 6c0a39
}
Packit 6c0a39
Packit 6c0a39
/* A very simple terminal emulator:
Packit 6c0a39
   - print data received from the remote computer
Packit 6c0a39
   - send keyboard input to the remote computer
Packit 6c0a39
*/
Packit 6c0a39
int interactive_shell_session(ssh_channel channel)
Packit 6c0a39
{
Packit 6c0a39
  /* Session and terminal initialization skipped */
Packit 6c0a39
  ...
Packit 6c0a39
Packit 6c0a39
  char buffer[256];
Packit 6c0a39
  int nbytes, nwritten;
Packit 6c0a39
Packit 6c0a39
  while (ssh_channel_is_open(channel) &&
Packit 6c0a39
         !ssh_channel_is_eof(channel))
Packit 6c0a39
  {
Packit 6c0a39
    nbytes = ssh_channel_read_nonblocking(channel, buffer, sizeof(buffer), 0);
Packit 6c0a39
    if (nbytes < 0) return SSH_ERROR;
Packit 6c0a39
    if (nbytes > 0)
Packit 6c0a39
    {
Packit 6c0a39
      nwritten = write(1, buffer, nbytes);
Packit 6c0a39
      if (nwritten != nbytes) return SSH_ERROR;
Packit 6c0a39
Packit 6c0a39
    if (!kbhit())
Packit 6c0a39
    {
Packit 6c0a39
      usleep(50000L); // 0.05 second
Packit 6c0a39
      continue;
Packit 6c0a39
    }
Packit 6c0a39
Packit 6c0a39
    nbytes = read(0, buffer, sizeof(buffer));
Packit 6c0a39
    if (nbytes < 0) return SSH_ERROR;
Packit 6c0a39
    if (nbytes > 0)
Packit 6c0a39
    {
Packit 6c0a39
      nwritten = ssh_channel_write(channel, buffer, nbytes);
Packit 6c0a39
      if (nwritten != nbytes) return SSH_ERROR;
Packit 6c0a39
    }
Packit 6c0a39
  }
Packit 6c0a39
Packit 6c0a39
  return rc;
Packit 6c0a39
}
Packit 6c0a39
@endcode
Packit 6c0a39
Packit 6c0a39
Of course, this is a poor terminal emulator, since the echo from the keys
Packit 6c0a39
pressed should not be done locally, but should be done by the remote side.
Packit 6c0a39
Also, user's input should not be sent once "Enter" key is pressed, but
Packit 6c0a39
immediately after each key is pressed. This can be accomplished
Packit 6c0a39
by setting the local terminal to "raw" mode with the cfmakeraw(3) function.
Packit 6c0a39
cfmakeraw() is a standard function under Linux, on other systems you can
Packit 6c0a39
recode it with:
Packit 6c0a39
Packit 6c0a39
@code
Packit 6c0a39
static void cfmakeraw(struct termios *termios_p)
Packit 6c0a39
{
Packit 6c0a39
    termios_p->c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON);
Packit 6c0a39
    termios_p->c_oflag &= ~OPOST;
Packit 6c0a39
    termios_p->c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN);
Packit 6c0a39
    termios_p->c_cflag &= ~(CSIZE|PARENB);
Packit 6c0a39
    termios_p->c_cflag |= CS8;
Packit 6c0a39
}
Packit 6c0a39
@endcode
Packit 6c0a39
Packit 6c0a39
If you are not using a local terminal, but some kind of graphical
Packit 6c0a39
environment, the solution to this kind of "echo" problems will be different.
Packit 6c0a39
Packit 6c0a39
Packit 6c0a39
@subsection select_loop A more elaborate way to get the remote data
Packit 6c0a39
Packit 6c0a39
*** Warning: ssh_select() and ssh_channel_select() are not relevant anymore,
Packit 6c0a39
    since libssh is about to provide an easier system for asynchronous
Packit 6c0a39
    communications. This subsection should be removed then. ***
Packit 6c0a39
Packit 6c0a39
ssh_channel_read() and ssh_channel_read_nonblocking() functions are simple,
Packit 6c0a39
but they are not adapted when you expect data from more than one SSH channel,
Packit 6c0a39
or from other file descriptors. Last example showed how getting data from
Packit 6c0a39
the standard input (the keyboard) at the same time as data from the SSH
Packit 6c0a39
channel was complicated. The functions ssh_select() and ssh_channel_select()
Packit 6c0a39
provide a more elegant way to wait for data coming from many sources.
Packit 6c0a39
Packit 6c0a39
The functions ssh_select() and ssh_channel_select() remind of the standard
Packit 6c0a39
UNIX select(2) function. The idea is to wait for "something" to happen:
Packit 6c0a39
incoming data to be read, outgoing data to block, or an exception to
Packit 6c0a39
occur. Both these functions do a "passive wait", i.e. you can safely use
Packit 6c0a39
them repeatedly in a loop, it will not consume exaggerate processor time
Packit 6c0a39
and make your computer unresponsive. It is quite common to use these
Packit 6c0a39
functions in your application's main loop.
Packit 6c0a39
Packit 6c0a39
The difference between ssh_select() and ssh_channel_select() is that
Packit 6c0a39
ssh_channel_select() is simpler, but allows you only to watch SSH channels.
Packit 6c0a39
ssh_select() is more complete and enables watching regular file descriptors
Packit 6c0a39
as well, in the same function call.
Packit 6c0a39
Packit 6c0a39
Below is an example of a function that waits both for remote SSH data to come,
Packit 6c0a39
as well as standard input from the keyboard:
Packit 6c0a39
Packit 6c0a39
@code
Packit 6c0a39
int interactive_shell_session(ssh_session session, ssh_channel channel)
Packit 6c0a39
{
Packit 6c0a39
  /* Session and terminal initialization skipped */
Packit 6c0a39
  ...
Packit 6c0a39
Packit 6c0a39
  char buffer[256];
Packit 6c0a39
  int nbytes, nwritten;
Packit 6c0a39
Packit 6c0a39
  while (ssh_channel_is_open(channel) &&
Packit 6c0a39
         !ssh_channel_is_eof(channel))
Packit 6c0a39
  {
Packit 6c0a39
    struct timeval timeout;
Packit 6c0a39
    ssh_channel in_channels[2], out_channels[2];
Packit 6c0a39
    fd_set fds;
Packit 6c0a39
    int maxfd;
Packit 6c0a39
Packit 6c0a39
    timeout.tv_sec = 30;
Packit 6c0a39
    timeout.tv_usec = 0;
Packit 6c0a39
    in_channels[0] = channel;
Packit 6c0a39
    in_channels[1] = NULL;
Packit 6c0a39
    FD_ZERO(&fds);
Packit 6c0a39
    FD_SET(0, &fds);
Packit 6c0a39
    FD_SET(ssh_get_fd(session), &fds);
Packit 6c0a39
    maxfd = ssh_get_fd(session) + 1;
Packit 6c0a39
Packit 6c0a39
    ssh_select(in_channels, out_channels, maxfd, &fds, &timeout);
Packit 6c0a39
Packit 6c0a39
    if (out_channels[0] != NULL)
Packit 6c0a39
    {
Packit 6c0a39
      nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0);
Packit 6c0a39
      if (nbytes < 0) return SSH_ERROR;
Packit 6c0a39
      if (nbytes > 0)
Packit 6c0a39
      {
Packit 6c0a39
        nwritten = write(1, buffer, nbytes);
Packit 6c0a39
        if (nwritten != nbytes) return SSH_ERROR;
Packit 6c0a39
      }
Packit 6c0a39
    }
Packit 6c0a39
Packit 6c0a39
    if (FD_ISSET(0, &fds))
Packit 6c0a39
    {
Packit 6c0a39
      nbytes = read(0, buffer, sizeof(buffer));
Packit 6c0a39
      if (nbytes < 0) return SSH_ERROR;
Packit 6c0a39
      if (nbytes > 0)
Packit 6c0a39
      {
Packit 6c0a39
        nwritten = ssh_channel_write(channel, buffer, nbytes);
Packit 6c0a39
        if (nbytes != nwritten) return SSH_ERROR;
Packit 6c0a39
      }
Packit 6c0a39
    }
Packit 6c0a39
  }
Packit 6c0a39
Packit 6c0a39
  return rc;
Packit 6c0a39
}
Packit 6c0a39
@endcode
Packit 6c0a39
Packit 6c0a39
Packit 6c0a39
@subsection x11 Using graphical applications on the remote side
Packit 6c0a39
Packit 6c0a39
If your remote application is graphical, you can forward the X11 protocol to
Packit 6c0a39
your local computer.
Packit 6c0a39
Packit 6c0a39
To do that, you first declare that you accept X11 connections with
Packit 6c0a39
ssh_channel_accept_x11(). Then you create the forwarding tunnel for
Packit 6c0a39
the X11 protocol with ssh_channel_request_x11().
Packit 6c0a39
Packit 6c0a39
The following code performs channel initialization and shell session
Packit 6c0a39
opening, and handles a parallel X11 connection:
Packit 6c0a39
Packit 6c0a39
@code
Packit 6c0a39
int interactive_shell_session(ssh_channel channel)
Packit 6c0a39
{
Packit 6c0a39
  int rc;
Packit 6c0a39
  ssh_channel x11channel;
Packit 6c0a39
Packit 6c0a39
  rc = ssh_channel_request_pty(channel);
Packit 6c0a39
  if (rc != SSH_OK) return rc;
Packit 6c0a39
Packit 6c0a39
  rc = ssh_channel_change_pty_size(channel, 80, 24);
Packit 6c0a39
  if (rc != SSH_OK) return rc;
Packit 6c0a39
Packit 6c0a39
  rc = ssh_channel_request_x11(channel, 0, NULL, NULL, 0);
Packit 6c0a39
  if (rc != SSH_OK) return rc;
Packit 6c0a39
Packit 6c0a39
  rc = ssh_channel_request_shell(channel);
Packit 6c0a39
  if (rc != SSH_OK) return rc;
Packit 6c0a39
Packit 6c0a39
  /* Read the data sent by the remote computer here */
Packit 6c0a39
  ...
Packit 6c0a39
}
Packit 6c0a39
@endcode
Packit 6c0a39
Packit 6c0a39
Don't forget to set the $DISPLAY environment variable on the remote
Packit 6c0a39
side, or the remote applications won't try using the X11 tunnel:
Packit 6c0a39
Packit 6c0a39
@code
Packit 6c0a39
$ export DISPLAY=:0
Packit 6c0a39
$ xclock &
Packit 6c0a39
@endcode
Packit 6c0a39
Packit 6c0a39
*/