aboutsummaryrefslogtreecommitdiff
path: root/doc/shell.dox
blob: 35fdfc826129bdceb7e3174dddca1656ce0e3bf3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
/**
@page libssh_tutor_shell Chapter 3: Opening a remote shell
@section opening_shell Opening a remote shell

We already mentioned that a single SSH connection can be shared
between several "channels". Channels can be used for different purposes.

This chapter shows how to open one of these channels, and how to use it to
start a command interpreter on a remote computer.


@subsection open_channel Opening and closing a channel

The ssh_channel_new() function creates a channel. It returns the channel as
a variable of type ssh_channel.

Once you have this channel, you open a SSH session that uses it with
ssh_channel_open_session().

Once you don't need the channel anymore, you can send an end-of-file
to it with ssh_channel_close(). At this point, you can destroy the channel
with ssh_channel_free().

The code sample below achieves these tasks:

@code
int shell_session(ssh_session session)
{
  ssh_channel channel;
  int rc;

  channel = ssh_channel_new(session);
  if (channel == NULL)
    return SSH_ERROR;

  rc = ssh_channel_open_session(channel);
  if (rc != SSH_OK)
  {
    ssh_channel_free(channel);
    return rc;
  }

  ...

  ssh_channel_close(channel);
  ssh_channel_send_eof(channel);
  ssh_channel_free(channel);

  return SSH_OK;
}
@endcode


@subsection interactive Interactive and non-interactive sessions

A "shell" is a command interpreter. It is said to be "interactive"
if there is a human user typing the commands, one after the
other. The contrary, a non-interactive shell, is similar to
the execution of commands in the background: there is no attached
terminal.

If you plan using an interactive shell, you need to create a
pseud-terminal on the remote side. A remote terminal is usually referred
to as a "pty", for "pseudo-teletype". The remote processes won't see the
difference with a real text-oriented terminal.

If needed, you request the pty with the function ssh_channel_request_pty().
Then you define its dimensions (number of rows and columns)
with ssh_channel_change_pty_size().

Be your session interactive or not, the next step is to request a
shell with ssh_channel_request_shell().

@code
int interactive_shell_session(ssh_channel channel)
{
  int rc;

  rc = ssh_channel_request_pty(channel);
  if (rc != SSH_OK) return rc;

  rc = ssh_channel_change_pty_size(channel, 80, 24);
  if (rc != SSH_OK) return rc;

  rc = ssh_channel_request_shell(channel);
  if (rc != SSH_OK) return rc;

  ...

  return rc;
}
@endcode


@subsection read_data Displaying the data sent by the remote computer

In your program, you will usually need to receive all the data "displayed"
into the remote pty. You will usually analyse, log, or display this data.

ssh_channel_read() and ssh_channel_read_nonblocking() are the simplest
way to read data from a channel. If you only need to read from a single
channel, they should be enough.

The example below shows how to wait for remote data using ssh_channel_read():

@code
int interactive_shell_session(ssh_channel channel)
{
  int rc;
  char buffer[256];
  int nbytes;

  rc = ssh_channel_request_pty(channel);
  if (rc != SSH_OK) return rc;

  rc = ssh_channel_change_pty_size(channel, 80, 24);
  if (rc != SSH_OK) return rc;

  rc = ssh_channel_request_shell(channel);
  if (rc != SSH_OK) return rc;

  while (ssh_channel_is_open(channel) &&
         !ssh_channel_is_eof(channel))
  {
    nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0);
    if (nbytes < 0)
      return SSH_ERROR;

    if (nbytes > 0)
      write(1, buffer, nbytes);
  }

  return rc;
}
@endcode

Unlike ssh_channel_read(), ssh_channel_read_nonblocking() never waits for
remote data to be ready. It returns immediately.

If you plan to use ssh_channel_read_nonblocking() repeatedly in a loop,
you should use a "passive wait" function like usleep(3) in the same
loop. Otherwise, your program will consume all the CPU time, and your
computer might become unresponsive.


@subsection write_data Sending user input to the remote computer

User's input is sent to the remote site with ssh_channel_write().

The following example shows how to combine a nonblocking read from a SSH
channel with a nonblocking read from the keyboard. The local input is then
sent to the remote computer:

@code
/* Under Linux, this function determines whether a key has been pressed.
   Under Windows, it is a standard function, so you need not redefine it.
*/
int kbhit()
{
    struct timeval tv = { 0L, 0L };
    fd_set fds;

    FD_ZERO(&fds);
    FD_SET(0, &fds);

    return select(1, &fds, NULL, NULL, &tv);
}

/* A very simple terminal emulator:
   - print data received from the remote computer
   - send keyboard input to the remote computer
*/
int interactive_shell_session(ssh_channel channel)
{
  /* Session and terminal initialization skipped */
  ...

  char buffer[256];
  int nbytes, nwritten;

  while (ssh_channel_is_open(channel) &&
         !ssh_channel_is_eof(channel))
  {
    nbytes = ssh_channel_read_nonblocking(channel, buffer, sizeof(buffer), 0);
    if (nbytes < 0) return SSH_ERROR;
    if (nbytes > 0)
    {
      nwritten = write(1, buffer, nbytes);
      if (nwritten != nbytes) return SSH_ERROR;

    if (!kbhit())
    {
      usleep(50000L); // 0.05 second
      continue;
    }

    nbytes = read(0, buffer, sizeof(buffer));
    if (nbytes < 0) return SSH_ERROR;
    if (nbytes > 0)
    {
      nwritten = ssh_channel_write(channel, buffer, nbytes);
      if (nwritten != nbytes) return SSH_ERROR;
    }
  }

  return rc;
}
@endcode

Of course, this is a poor terminal emulator, since the echo from the keys
pressed should not be done locally, but should be done by the remote side.
Also, user's input should not be sent once "Enter" key is pressed, but 
immediately after each key is pressed. This can be accomplished
by setting the local terminal to "raw" mode with the cfmakeraw(3) function.
cfmakeraw() is a standard function under Linux, on other systems you can
recode it with:

@code
static void cfmakeraw(struct termios *termios_p)
{
    termios_p->c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON);
    termios_p->c_oflag &= ~OPOST;
    termios_p->c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN);
    termios_p->c_cflag &= ~(CSIZE|PARENB);
    termios_p->c_cflag |= CS8;
}
@endcode

If you are not using a local terminal, but some kind of graphical
environment, the solution to this kind of "echo" problems will be different.


@subsection select_loop A more elaborate way to get the remote data

*** Warning: ssh_select() and ssh_channel_select() are not relevant anymore,
    since libssh is about to provide an easier system for asynchronous
    communications. This subsection should be removed then. ***

ssh_channel_read() and ssh_channel_read_nonblocking() functions are simple,
but they are not adapted when you expect data from more than one SSH channel,
or from other file descriptors. Last example showed how getting data from
the standard input (the keyboard) at the same time as data from the SSH
channel was complicated. The functions ssh_select() and ssh_channel_select()
provide a more elegant way to wait for data coming from many sources.

The functions ssh_select() and ssh_channel_select() remind of the standard
UNIX select(2) function. The idea is to wait for "something" to happen:
incoming data to be read, outcoming data to block, or an exception to
occur. Both these functions do a "passive wait", i.e. you can safely use
them repeatedly in a loop, it will not consume exaggerate processor time
and make your computer unresponsive. It is quite common to use these
functions in your application's main loop.

The difference between ssh_select() and ssh_channel_select() is that 
ssh_channel_select() is simpler, but allows you only to watch SSH channels.
ssh_select() is more complete and enables watching regular file descriptors
as well, in the same function call.

Below is an example of a function that waits both for remote SSH data to come,
as well as standard input from the keyboard:

@code
int interactive_shell_session(ssh_session session, ssh_channel channel)
{
  /* Session and terminal initialization skipped */
  ...

  char buffer[256];
  int nbytes, nwritten;

  while (ssh_channel_is_open(channel) &&
         !ssh_channel_is_eof(channel))
  {
    struct timeval timeout;
    ssh_channel in_channels[2], out_channels[2];
    fd_set fds;
    int maxfd;

    timeout.tv_sec = 30;
    timeout.tv_usec = 0;
    in_channels[0] = channel;
    in_channels[1] = NULL;
    FD_ZERO(&fds);
    FD_SET(0, &fds);
    FD_SET(ssh_get_fd(session), &fds);
    maxfd = ssh_get_fd(session) + 1;

    ssh_select(in_channels, out_channels, maxfd, &fds, &timeout);

    if (out_channels[0] != NULL)
    {
      nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0);
      if (nbytes < 0) return SSH_ERROR;
      if (nbytes > 0)
      {
        nwritten = write(1, buffer, nbytes);
        if (nwritten != nbytes) return SSH_ERROR;
      }
    }

    if (FD_ISSET(0, &fds))
    {
      nbytes = read(0, buffer, sizeof(buffer));
      if (nbytes < 0) return SSH_ERROR;
      if (nbytes > 0)
      {
        nwritten = ssh_channel_write(channel, buffer, nbytes);
        if (nbytes != nwritten) return SSH_ERROR;
      }
    }
  }

  return rc;
}
@endcode


@subsection x11 Using graphical applications on the remote side

If your remote application is graphical, you can forward the X11 protocol to
your local computer.

To do that, you first declare that you accept X11 connections with
ssh_channel_accept_x11(). Then you create the forwarding tunnel for
the X11 protocol with ssh_channel_request_x11().

The following code performs channel initialization and shell session
opening, and handles a parallel X11 connection:

@code
int interactive_shell_session(ssh_channel channel)
{
  int rc;
  ssh_channel x11channel;

  rc = ssh_channel_request_pty(channel);
  if (rc != SSH_OK) return rc;

  rc = ssh_channel_change_pty_size(channel, 80, 24);
  if (rc != SSH_OK) return rc;

  rc = ssh_channel_request_x11(channel, 0, NULL, NULL, 0);
  if (rc != SSH_OK) return rc;

  rc = ssh_channel_request_shell(channel);
  if (rc != SSH_OK) return rc;

  /* Read the data sent by the remote computer here */
  ...
}
@endcode

Don't forget to set the $DISPLAY environment variable on the remote
side, or the remote applications won't try using the X11 tunnel:

@code
$ export DISPLAY=:0
$ xclock &
@endcode

*/