source: trunk/third/openssh/clientloop.c @ 18759

Revision 18759, 38.0 KB checked in by zacheiss, 22 years ago (diff)
This commit was generated by cvs2svn to compensate for changes in r18758, which included commits to RCS files with non-trunk default branches.
Line 
1/*
2 * Author: Tatu Ylonen <ylo@cs.hut.fi>
3 * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
4 *                    All rights reserved
5 * The main loop for the interactive session (client side).
6 *
7 * As far as I am concerned, the code I have written for this software
8 * can be used freely for any purpose.  Any derived versions of this
9 * software must be clearly marked as such, and if the derived work is
10 * incompatible with the protocol description in the RFC file, it must be
11 * called by a name other than "ssh" or "Secure Shell".
12 *
13 *
14 * Copyright (c) 1999 Theo de Raadt.  All rights reserved.
15 *
16 * Redistribution and use in source and binary forms, with or without
17 * modification, are permitted provided that the following conditions
18 * are met:
19 * 1. Redistributions of source code must retain the above copyright
20 *    notice, this list of conditions and the following disclaimer.
21 * 2. Redistributions in binary form must reproduce the above copyright
22 *    notice, this list of conditions and the following disclaimer in the
23 *    documentation and/or other materials provided with the distribution.
24 *
25 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
26 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
27 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
28 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
29 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
30 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
31 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
32 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
33 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
34 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35 *
36 *
37 * SSH2 support added by Markus Friedl.
38 * Copyright (c) 1999, 2000, 2001 Markus Friedl.  All rights reserved.
39 *
40 * Redistribution and use in source and binary forms, with or without
41 * modification, are permitted provided that the following conditions
42 * are met:
43 * 1. Redistributions of source code must retain the above copyright
44 *    notice, this list of conditions and the following disclaimer.
45 * 2. Redistributions in binary form must reproduce the above copyright
46 *    notice, this list of conditions and the following disclaimer in the
47 *    documentation and/or other materials provided with the distribution.
48 *
49 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
50 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
51 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
52 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
53 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
54 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
55 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
56 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
57 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
58 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
59 */
60
61#include "includes.h"
62RCSID("$OpenBSD: clientloop.c,v 1.104 2002/08/22 19:38:42 stevesk Exp $");
63
64#include "ssh.h"
65#include "ssh1.h"
66#include "ssh2.h"
67#include "xmalloc.h"
68#include "packet.h"
69#include "buffer.h"
70#include "compat.h"
71#include "channels.h"
72#include "dispatch.h"
73#include "buffer.h"
74#include "bufaux.h"
75#include "key.h"
76#include "kex.h"
77#include "log.h"
78#include "readconf.h"
79#include "clientloop.h"
80#include "authfd.h"
81#include "atomicio.h"
82#include "sshtty.h"
83#include "misc.h"
84#include "readpass.h"
85
86/* import options */
87extern Options options;
88
89/* Flag indicating that stdin should be redirected from /dev/null. */
90extern int stdin_null_flag;
91
92/*
93 * Name of the host we are connecting to.  This is the name given on the
94 * command line, or the HostName specified for the user-supplied name in a
95 * configuration file.
96 */
97extern char *host;
98
99/*
100 * Flag to indicate that we have received a window change signal which has
101 * not yet been processed.  This will cause a message indicating the new
102 * window size to be sent to the server a little later.  This is volatile
103 * because this is updated in a signal handler.
104 */
105static volatile sig_atomic_t received_window_change_signal = 0;
106static volatile sig_atomic_t received_signal = 0;
107
108/* Flag indicating whether the user\'s terminal is in non-blocking mode. */
109static int in_non_blocking_mode = 0;
110
111/* Common data for the client loop code. */
112static int quit_pending;        /* Set to non-zero to quit the client loop. */
113static int escape_char;         /* Escape character. */
114static int escape_pending;      /* Last character was the escape character */
115static int last_was_cr;         /* Last character was a newline. */
116static int exit_status;         /* Used to store the exit status of the command. */
117static int stdin_eof;           /* EOF has been encountered on standard error. */
118static Buffer stdin_buffer;     /* Buffer for stdin data. */
119static Buffer stdout_buffer;    /* Buffer for stdout data. */
120static Buffer stderr_buffer;    /* Buffer for stderr data. */
121static u_long stdin_bytes, stdout_bytes, stderr_bytes;
122static u_int buffer_high;/* Soft max buffer size. */
123static int connection_in;       /* Connection to server (input). */
124static int connection_out;      /* Connection to server (output). */
125static int need_rekeying;       /* Set to non-zero if rekeying is requested. */
126static int session_closed = 0;  /* In SSH2: login session closed. */
127
128static void client_init_dispatch(void);
129int     session_ident = -1;
130
131/*XXX*/
132extern Kex *xxx_kex;
133
134/* Restores stdin to blocking mode. */
135
136static void
137leave_non_blocking(void)
138{
139        if (in_non_blocking_mode) {
140                (void) fcntl(fileno(stdin), F_SETFL, 0);
141                in_non_blocking_mode = 0;
142                fatal_remove_cleanup((void (*) (void *)) leave_non_blocking, NULL);
143        }
144}
145
146/* Puts stdin terminal in non-blocking mode. */
147
148static void
149enter_non_blocking(void)
150{
151        in_non_blocking_mode = 1;
152        (void) fcntl(fileno(stdin), F_SETFL, O_NONBLOCK);
153        fatal_add_cleanup((void (*) (void *)) leave_non_blocking, NULL);
154}
155
156/*
157 * Signal handler for the window change signal (SIGWINCH).  This just sets a
158 * flag indicating that the window has changed.
159 */
160
161static void
162window_change_handler(int sig)
163{
164        received_window_change_signal = 1;
165        signal(SIGWINCH, window_change_handler);
166}
167
168/*
169 * Signal handler for signals that cause the program to terminate.  These
170 * signals must be trapped to restore terminal modes.
171 */
172
173static void
174signal_handler(int sig)
175{
176        received_signal = sig;
177        quit_pending = 1;
178}
179
180/*
181 * Returns current time in seconds from Jan 1, 1970 with the maximum
182 * available resolution.
183 */
184
185static double
186get_current_time(void)
187{
188        struct timeval tv;
189        gettimeofday(&tv, NULL);
190        return (double) tv.tv_sec + (double) tv.tv_usec / 1000000.0;
191}
192
193/*
194 * This is called when the interactive is entered.  This checks if there is
195 * an EOF coming on stdin.  We must check this explicitly, as select() does
196 * not appear to wake up when redirecting from /dev/null.
197 */
198
199static void
200client_check_initial_eof_on_stdin(void)
201{
202        int len;
203        char buf[1];
204
205        /*
206         * If standard input is to be "redirected from /dev/null", we simply
207         * mark that we have seen an EOF and send an EOF message to the
208         * server. Otherwise, we try to read a single character; it appears
209         * that for some files, such /dev/null, select() never wakes up for
210         * read for this descriptor, which means that we never get EOF.  This
211         * way we will get the EOF if stdin comes from /dev/null or similar.
212         */
213        if (stdin_null_flag) {
214                /* Fake EOF on stdin. */
215                debug("Sending eof.");
216                stdin_eof = 1;
217                packet_start(SSH_CMSG_EOF);
218                packet_send();
219        } else {
220                enter_non_blocking();
221
222                /* Check for immediate EOF on stdin. */
223                len = read(fileno(stdin), buf, 1);
224                if (len == 0) {
225                        /* EOF.  Record that we have seen it and send EOF to server. */
226                        debug("Sending eof.");
227                        stdin_eof = 1;
228                        packet_start(SSH_CMSG_EOF);
229                        packet_send();
230                } else if (len > 0) {
231                        /*
232                         * Got data.  We must store the data in the buffer,
233                         * and also process it as an escape character if
234                         * appropriate.
235                         */
236                        if ((u_char) buf[0] == escape_char)
237                                escape_pending = 1;
238                        else
239                                buffer_append(&stdin_buffer, buf, 1);
240                }
241                leave_non_blocking();
242        }
243}
244
245
246/*
247 * Make packets from buffered stdin data, and buffer them for sending to the
248 * connection.
249 */
250
251static void
252client_make_packets_from_stdin_data(void)
253{
254        u_int len;
255
256        /* Send buffered stdin data to the server. */
257        while (buffer_len(&stdin_buffer) > 0 &&
258            packet_not_very_much_data_to_write()) {
259                len = buffer_len(&stdin_buffer);
260                /* Keep the packets at reasonable size. */
261                if (len > packet_get_maxsize())
262                        len = packet_get_maxsize();
263                packet_start(SSH_CMSG_STDIN_DATA);
264                packet_put_string(buffer_ptr(&stdin_buffer), len);
265                packet_send();
266                buffer_consume(&stdin_buffer, len);
267                stdin_bytes += len;
268                /* If we have a pending EOF, send it now. */
269                if (stdin_eof && buffer_len(&stdin_buffer) == 0) {
270                        packet_start(SSH_CMSG_EOF);
271                        packet_send();
272                }
273        }
274}
275
276/*
277 * Checks if the client window has changed, and sends a packet about it to
278 * the server if so.  The actual change is detected elsewhere (by a software
279 * interrupt on Unix); this just checks the flag and sends a message if
280 * appropriate.
281 */
282
283static void
284client_check_window_change(void)
285{
286        struct winsize ws;
287
288        if (! received_window_change_signal)
289                return;
290        /** XXX race */
291        received_window_change_signal = 0;
292
293        if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) < 0)
294                return;
295
296        debug2("client_check_window_change: changed");
297
298        if (compat20) {
299                channel_request_start(session_ident, "window-change", 0);
300                packet_put_int(ws.ws_col);
301                packet_put_int(ws.ws_row);
302                packet_put_int(ws.ws_xpixel);
303                packet_put_int(ws.ws_ypixel);
304                packet_send();
305        } else {
306                packet_start(SSH_CMSG_WINDOW_SIZE);
307                packet_put_int(ws.ws_row);
308                packet_put_int(ws.ws_col);
309                packet_put_int(ws.ws_xpixel);
310                packet_put_int(ws.ws_ypixel);
311                packet_send();
312        }
313}
314
315/*
316 * Waits until the client can do something (some data becomes available on
317 * one of the file descriptors).
318 */
319
320static void
321client_wait_until_can_do_something(fd_set **readsetp, fd_set **writesetp,
322    int *maxfdp, int *nallocp, int rekeying)
323{
324        /* Add any selections by the channel mechanism. */
325        channel_prepare_select(readsetp, writesetp, maxfdp, nallocp, rekeying);
326
327        if (!compat20) {
328                /* Read from the connection, unless our buffers are full. */
329                if (buffer_len(&stdout_buffer) < buffer_high &&
330                    buffer_len(&stderr_buffer) < buffer_high &&
331                    channel_not_very_much_buffered_data())
332                        FD_SET(connection_in, *readsetp);
333                /*
334                 * Read from stdin, unless we have seen EOF or have very much
335                 * buffered data to send to the server.
336                 */
337                if (!stdin_eof && packet_not_very_much_data_to_write())
338                        FD_SET(fileno(stdin), *readsetp);
339
340                /* Select stdout/stderr if have data in buffer. */
341                if (buffer_len(&stdout_buffer) > 0)
342                        FD_SET(fileno(stdout), *writesetp);
343                if (buffer_len(&stderr_buffer) > 0)
344                        FD_SET(fileno(stderr), *writesetp);
345        } else {
346                /* channel_prepare_select could have closed the last channel */
347                if (session_closed && !channel_still_open() &&
348                    !packet_have_data_to_write()) {
349                        /* clear mask since we did not call select() */
350                        memset(*readsetp, 0, *nallocp);
351                        memset(*writesetp, 0, *nallocp);
352                        return;
353                } else {
354                        FD_SET(connection_in, *readsetp);
355                }
356        }
357
358        /* Select server connection if have data to write to the server. */
359        if (packet_have_data_to_write())
360                FD_SET(connection_out, *writesetp);
361
362        /*
363         * Wait for something to happen.  This will suspend the process until
364         * some selected descriptor can be read, written, or has some other
365         * event pending. Note: if you want to implement SSH_MSG_IGNORE
366         * messages to fool traffic analysis, this might be the place to do
367         * it: just have a random timeout for the select, and send a random
368         * SSH_MSG_IGNORE packet when the timeout expires.
369         */
370
371        if (select((*maxfdp)+1, *readsetp, *writesetp, NULL, NULL) < 0) {
372                char buf[100];
373
374                /*
375                 * We have to clear the select masks, because we return.
376                 * We have to return, because the mainloop checks for the flags
377                 * set by the signal handlers.
378                 */
379                memset(*readsetp, 0, *nallocp);
380                memset(*writesetp, 0, *nallocp);
381
382                if (errno == EINTR)
383                        return;
384                /* Note: we might still have data in the buffers. */
385                snprintf(buf, sizeof buf, "select: %s\r\n", strerror(errno));
386                buffer_append(&stderr_buffer, buf, strlen(buf));
387                quit_pending = 1;
388        }
389}
390
391static void
392client_suspend_self(Buffer *bin, Buffer *bout, Buffer *berr)
393{
394        struct winsize oldws, newws;
395
396        /* Flush stdout and stderr buffers. */
397        if (buffer_len(bout) > 0)
398                atomicio(write, fileno(stdout), buffer_ptr(bout), buffer_len(bout));
399        if (buffer_len(berr) > 0)
400                atomicio(write, fileno(stderr), buffer_ptr(berr), buffer_len(berr));
401
402        leave_raw_mode();
403
404        /*
405         * Free (and clear) the buffer to reduce the amount of data that gets
406         * written to swap.
407         */
408        buffer_free(bin);
409        buffer_free(bout);
410        buffer_free(berr);
411
412        /* Save old window size. */
413        ioctl(fileno(stdin), TIOCGWINSZ, &oldws);
414
415        /* Send the suspend signal to the program itself. */
416        kill(getpid(), SIGTSTP);
417
418        /* Check if the window size has changed. */
419        if (ioctl(fileno(stdin), TIOCGWINSZ, &newws) >= 0 &&
420            (oldws.ws_row != newws.ws_row ||
421            oldws.ws_col != newws.ws_col ||
422            oldws.ws_xpixel != newws.ws_xpixel ||
423            oldws.ws_ypixel != newws.ws_ypixel))
424                received_window_change_signal = 1;
425
426        /* OK, we have been continued by the user. Reinitialize buffers. */
427        buffer_init(bin);
428        buffer_init(bout);
429        buffer_init(berr);
430
431        enter_raw_mode();
432}
433
434static void
435client_process_net_input(fd_set * readset)
436{
437        int len;
438        char buf[8192];
439
440        /*
441         * Read input from the server, and add any such data to the buffer of
442         * the packet subsystem.
443         */
444        if (FD_ISSET(connection_in, readset)) {
445                /* Read as much as possible. */
446                len = read(connection_in, buf, sizeof(buf));
447                if (len == 0) {
448                        /* Received EOF.  The remote host has closed the connection. */
449                        snprintf(buf, sizeof buf, "Connection to %.300s closed by remote host.\r\n",
450                                 host);
451                        buffer_append(&stderr_buffer, buf, strlen(buf));
452                        quit_pending = 1;
453                        return;
454                }
455                /*
456                 * There is a kernel bug on Solaris that causes select to
457                 * sometimes wake up even though there is no data available.
458                 */
459                if (len < 0 && (errno == EAGAIN || errno == EINTR))
460                        len = 0;
461
462                if (len < 0) {
463                        /* An error has encountered.  Perhaps there is a network problem. */
464                        snprintf(buf, sizeof buf, "Read from remote host %.300s: %.100s\r\n",
465                                 host, strerror(errno));
466                        buffer_append(&stderr_buffer, buf, strlen(buf));
467                        quit_pending = 1;
468                        return;
469                }
470                packet_process_incoming(buf, len);
471        }
472}
473
474static void
475process_cmdline(void)
476{
477        void (*handler)(int);
478        char *s, *cmd;
479        u_short fwd_port, fwd_host_port;
480        char buf[1024], sfwd_port[6], sfwd_host_port[6];
481        int local = 0;
482
483        leave_raw_mode();
484        handler = signal(SIGINT, SIG_IGN);
485        cmd = s = read_passphrase("\r\nssh> ", RP_ECHO);
486        if (s == NULL)
487                goto out;
488        while (*s && isspace(*s))
489                s++;
490        if (*s == 0)
491                goto out;
492        if (strlen(s) < 2 || s[0] != '-' || !(s[1] == 'L' || s[1] == 'R')) {
493                log("Invalid command.");
494                goto out;
495        }
496        if (s[1] == 'L')
497                local = 1;
498        if (!local && !compat20) {
499                log("Not supported for SSH protocol version 1.");
500                goto out;
501        }
502        s += 2;
503        while (*s && isspace(*s))
504                s++;
505
506        if (sscanf(s, "%5[0-9]:%255[^:]:%5[0-9]",
507            sfwd_port, buf, sfwd_host_port) != 3 &&
508            sscanf(s, "%5[0-9]/%255[^/]/%5[0-9]",
509            sfwd_port, buf, sfwd_host_port) != 3) {
510                log("Bad forwarding specification.");
511                goto out;
512        }
513        if ((fwd_port = a2port(sfwd_port)) == 0 ||
514            (fwd_host_port = a2port(sfwd_host_port)) == 0) {
515                log("Bad forwarding port(s).");
516                goto out;
517        }
518        if (local) {
519                if (channel_setup_local_fwd_listener(fwd_port, buf,
520                    fwd_host_port, options.gateway_ports) < 0) {
521                        log("Port forwarding failed.");
522                        goto out;
523                }
524        } else
525                channel_request_remote_forwarding(fwd_port, buf,
526                    fwd_host_port);
527        log("Forwarding port.");
528out:
529        signal(SIGINT, handler);
530        enter_raw_mode();
531        if (cmd)
532                xfree(cmd);
533}
534
535/* process the characters one by one */
536static int
537process_escapes(Buffer *bin, Buffer *bout, Buffer *berr, char *buf, int len)
538{
539        char string[1024];
540        pid_t pid;
541        int bytes = 0;
542        u_int i;
543        u_char ch;
544        char *s;
545
546        for (i = 0; i < len; i++) {
547                /* Get one character at a time. */
548                ch = buf[i];
549
550                if (escape_pending) {
551                        /* We have previously seen an escape character. */
552                        /* Clear the flag now. */
553                        escape_pending = 0;
554
555                        /* Process the escaped character. */
556                        switch (ch) {
557                        case '.':
558                                /* Terminate the connection. */
559                                snprintf(string, sizeof string, "%c.\r\n", escape_char);
560                                buffer_append(berr, string, strlen(string));
561
562                                quit_pending = 1;
563                                return -1;
564
565                        case 'Z' - 64:
566                                /* Suspend the program. */
567                                /* Print a message to that effect to the user. */
568                                snprintf(string, sizeof string, "%c^Z [suspend ssh]\r\n", escape_char);
569                                buffer_append(berr, string, strlen(string));
570
571                                /* Restore terminal modes and suspend. */
572                                client_suspend_self(bin, bout, berr);
573
574                                /* We have been continued. */
575                                continue;
576
577                        case 'R':
578                                if (compat20) {
579                                        if (datafellows & SSH_BUG_NOREKEY)
580                                                log("Server does not support re-keying");
581                                        else
582                                                need_rekeying = 1;
583                                }
584                                continue;
585
586                        case '&':
587                                /*
588                                 * Detach the program (continue to serve connections,
589                                 * but put in background and no more new connections).
590                                 */
591                                /* Restore tty modes. */
592                                leave_raw_mode();
593
594                                /* Stop listening for new connections. */
595                                channel_stop_listening();
596
597                                snprintf(string, sizeof string,
598                                    "%c& [backgrounded]\n", escape_char);
599                                buffer_append(berr, string, strlen(string));
600
601                                /* Fork into background. */
602                                pid = fork();
603                                if (pid < 0) {
604                                        error("fork: %.100s", strerror(errno));
605                                        continue;
606                                }
607                                if (pid != 0) { /* This is the parent. */
608                                        /* The parent just exits. */
609                                        exit(0);
610                                }
611                                /* The child continues serving connections. */
612                                if (compat20) {
613                                        buffer_append(bin, "\004", 1);
614                                        /* fake EOF on stdin */
615                                        return -1;
616                                } else if (!stdin_eof) {
617                                        /*
618                                         * Sending SSH_CMSG_EOF alone does not always appear
619                                         * to be enough.  So we try to send an EOF character
620                                         * first.
621                                         */
622                                        packet_start(SSH_CMSG_STDIN_DATA);
623                                        packet_put_string("\004", 1);
624                                        packet_send();
625                                        /* Close stdin. */
626                                        stdin_eof = 1;
627                                        if (buffer_len(bin) == 0) {
628                                                packet_start(SSH_CMSG_EOF);
629                                                packet_send();
630                                        }
631                                }
632                                continue;
633
634                        case '?':
635                                snprintf(string, sizeof string,
636"%c?\r\n\
637Supported escape sequences:\r\n\
638%c.  - terminate connection\r\n\
639%cC  - open a command line\r\n\
640%cR  - Request rekey (SSH protocol 2 only)\r\n\
641%c^Z - suspend ssh\r\n\
642%c#  - list forwarded connections\r\n\
643%c&  - background ssh (when waiting for connections to terminate)\r\n\
644%c?  - this message\r\n\
645%c%c  - send the escape character by typing it twice\r\n\
646(Note that escapes are only recognized immediately after newline.)\r\n",
647                                    escape_char, escape_char, escape_char, escape_char,
648                                    escape_char, escape_char, escape_char, escape_char,
649                                    escape_char, escape_char);
650                                buffer_append(berr, string, strlen(string));
651                                continue;
652
653                        case '#':
654                                snprintf(string, sizeof string, "%c#\r\n", escape_char);
655                                buffer_append(berr, string, strlen(string));
656                                s = channel_open_message();
657                                buffer_append(berr, s, strlen(s));
658                                xfree(s);
659                                continue;
660
661                        case 'C':
662                                process_cmdline();
663                                continue;
664
665                        default:
666                                if (ch != escape_char) {
667                                        buffer_put_char(bin, escape_char);
668                                        bytes++;
669                                }
670                                /* Escaped characters fall through here */
671                                break;
672                        }
673                } else {
674                        /*
675                         * The previous character was not an escape char. Check if this
676                         * is an escape.
677                         */
678                        if (last_was_cr && ch == escape_char) {
679                                /* It is. Set the flag and continue to next character. */
680                                escape_pending = 1;
681                                continue;
682                        }
683                }
684
685                /*
686                 * Normal character.  Record whether it was a newline,
687                 * and append it to the buffer.
688                 */
689                last_was_cr = (ch == '\r' || ch == '\n');
690                buffer_put_char(bin, ch);
691                bytes++;
692        }
693        return bytes;
694}
695
696static void
697client_process_input(fd_set * readset)
698{
699        int len;
700        char buf[8192];
701
702        /* Read input from stdin. */
703        if (FD_ISSET(fileno(stdin), readset)) {
704                /* Read as much as possible. */
705                len = read(fileno(stdin), buf, sizeof(buf));
706                if (len < 0 && (errno == EAGAIN || errno == EINTR))
707                        return;         /* we'll try again later */
708                if (len <= 0) {
709                        /*
710                         * Received EOF or error.  They are treated
711                         * similarly, except that an error message is printed
712                         * if it was an error condition.
713                         */
714                        if (len < 0) {
715                                snprintf(buf, sizeof buf, "read: %.100s\r\n", strerror(errno));
716                                buffer_append(&stderr_buffer, buf, strlen(buf));
717                        }
718                        /* Mark that we have seen EOF. */
719                        stdin_eof = 1;
720                        /*
721                         * Send an EOF message to the server unless there is
722                         * data in the buffer.  If there is data in the
723                         * buffer, no message will be sent now.  Code
724                         * elsewhere will send the EOF when the buffer
725                         * becomes empty if stdin_eof is set.
726                         */
727                        if (buffer_len(&stdin_buffer) == 0) {
728                                packet_start(SSH_CMSG_EOF);
729                                packet_send();
730                        }
731                } else if (escape_char == SSH_ESCAPECHAR_NONE) {
732                        /*
733                         * Normal successful read, and no escape character.
734                         * Just append the data to buffer.
735                         */
736                        buffer_append(&stdin_buffer, buf, len);
737                } else {
738                        /*
739                         * Normal, successful read.  But we have an escape character
740                         * and have to process the characters one by one.
741                         */
742                        if (process_escapes(&stdin_buffer, &stdout_buffer,
743                            &stderr_buffer, buf, len) == -1)
744                                return;
745                }
746        }
747}
748
749static void
750client_process_output(fd_set * writeset)
751{
752        int len;
753        char buf[100];
754
755        /* Write buffered output to stdout. */
756        if (FD_ISSET(fileno(stdout), writeset)) {
757                /* Write as much data as possible. */
758                len = write(fileno(stdout), buffer_ptr(&stdout_buffer),
759                    buffer_len(&stdout_buffer));
760                if (len <= 0) {
761                        if (errno == EINTR || errno == EAGAIN)
762                                len = 0;
763                        else {
764                                /*
765                                 * An error or EOF was encountered.  Put an
766                                 * error message to stderr buffer.
767                                 */
768                                snprintf(buf, sizeof buf, "write stdout: %.50s\r\n", strerror(errno));
769                                buffer_append(&stderr_buffer, buf, strlen(buf));
770                                quit_pending = 1;
771                                return;
772                        }
773                }
774                /* Consume printed data from the buffer. */
775                buffer_consume(&stdout_buffer, len);
776                stdout_bytes += len;
777        }
778        /* Write buffered output to stderr. */
779        if (FD_ISSET(fileno(stderr), writeset)) {
780                /* Write as much data as possible. */
781                len = write(fileno(stderr), buffer_ptr(&stderr_buffer),
782                    buffer_len(&stderr_buffer));
783                if (len <= 0) {
784                        if (errno == EINTR || errno == EAGAIN)
785                                len = 0;
786                        else {
787                                /* EOF or error, but can't even print error message. */
788                                quit_pending = 1;
789                                return;
790                        }
791                }
792                /* Consume printed characters from the buffer. */
793                buffer_consume(&stderr_buffer, len);
794                stderr_bytes += len;
795        }
796}
797
798/*
799 * Get packets from the connection input buffer, and process them as long as
800 * there are packets available.
801 *
802 * Any unknown packets received during the actual
803 * session cause the session to terminate.  This is
804 * intended to make debugging easier since no
805 * confirmations are sent.  Any compatible protocol
806 * extensions must be negotiated during the
807 * preparatory phase.
808 */
809
810static void
811client_process_buffered_input_packets(void)
812{
813        dispatch_run(DISPATCH_NONBLOCK, &quit_pending, compat20 ? xxx_kex : NULL);
814}
815
816/* scan buf[] for '~' before sending data to the peer */
817
818static int
819simple_escape_filter(Channel *c, char *buf, int len)
820{
821        /* XXX we assume c->extended is writeable */
822        return process_escapes(&c->input, &c->output, &c->extended, buf, len);
823}
824
825static void
826client_channel_closed(int id, void *arg)
827{
828        if (id != session_ident)
829                error("client_channel_closed: id %d != session_ident %d",
830                    id, session_ident);
831        channel_cancel_cleanup(id);
832        session_closed = 1;
833        if (in_raw_mode())
834                leave_raw_mode();
835}
836
837/*
838 * Implements the interactive session with the server.  This is called after
839 * the user has been authenticated, and a command has been started on the
840 * remote host.  If escape_char != SSH_ESCAPECHAR_NONE, it is the character
841 * used as an escape character for terminating or suspending the session.
842 */
843
844int
845client_loop(int have_pty, int escape_char_arg, int ssh2_chan_id)
846{
847        fd_set *readset = NULL, *writeset = NULL;
848        double start_time, total_time;
849        int max_fd = 0, max_fd2 = 0, len, rekeying = 0, nalloc = 0;
850        char buf[100];
851
852        debug("Entering interactive session.");
853
854        start_time = get_current_time();
855
856        /* Initialize variables. */
857        escape_pending = 0;
858        last_was_cr = 1;
859        exit_status = -1;
860        stdin_eof = 0;
861        buffer_high = 64 * 1024;
862        connection_in = packet_get_connection_in();
863        connection_out = packet_get_connection_out();
864        max_fd = MAX(connection_in, connection_out);
865
866        if (!compat20) {
867                /* enable nonblocking unless tty */
868                if (!isatty(fileno(stdin)))
869                        set_nonblock(fileno(stdin));
870                if (!isatty(fileno(stdout)))
871                        set_nonblock(fileno(stdout));
872                if (!isatty(fileno(stderr)))
873                        set_nonblock(fileno(stderr));
874                max_fd = MAX(max_fd, fileno(stdin));
875                max_fd = MAX(max_fd, fileno(stdout));
876                max_fd = MAX(max_fd, fileno(stderr));
877        }
878        stdin_bytes = 0;
879        stdout_bytes = 0;
880        stderr_bytes = 0;
881        quit_pending = 0;
882        escape_char = escape_char_arg;
883
884        /* Initialize buffers. */
885        buffer_init(&stdin_buffer);
886        buffer_init(&stdout_buffer);
887        buffer_init(&stderr_buffer);
888
889        client_init_dispatch();
890
891        /* Set signal handlers to restore non-blocking mode.  */
892        signal(SIGINT, signal_handler);
893        signal(SIGQUIT, signal_handler);
894        signal(SIGTERM, signal_handler);
895        if (have_pty)
896                signal(SIGWINCH, window_change_handler);
897
898        if (have_pty)
899                enter_raw_mode();
900
901        if (compat20) {
902                session_ident = ssh2_chan_id;
903                if (escape_char != SSH_ESCAPECHAR_NONE)
904                        channel_register_filter(session_ident,
905                            simple_escape_filter);
906                if (session_ident != -1)
907                        channel_register_cleanup(session_ident,
908                            client_channel_closed);
909        } else {
910                /* Check if we should immediately send eof on stdin. */
911                client_check_initial_eof_on_stdin();
912        }
913
914        /* Main loop of the client for the interactive session mode. */
915        while (!quit_pending) {
916
917                /* Process buffered packets sent by the server. */
918                client_process_buffered_input_packets();
919
920                if (compat20 && session_closed && !channel_still_open())
921                        break;
922
923                rekeying = (xxx_kex != NULL && !xxx_kex->done);
924
925                if (rekeying) {
926                        debug("rekeying in progress");
927                } else {
928                        /*
929                         * Make packets of buffered stdin data, and buffer
930                         * them for sending to the server.
931                         */
932                        if (!compat20)
933                                client_make_packets_from_stdin_data();
934
935                        /*
936                         * Make packets from buffered channel data, and
937                         * enqueue them for sending to the server.
938                         */
939                        if (packet_not_very_much_data_to_write())
940                                channel_output_poll();
941
942                        /*
943                         * Check if the window size has changed, and buffer a
944                         * message about it to the server if so.
945                         */
946                        client_check_window_change();
947
948                        if (quit_pending)
949                                break;
950                }
951                /*
952                 * Wait until we have something to do (something becomes
953                 * available on one of the descriptors).
954                 */
955                max_fd2 = max_fd;
956                client_wait_until_can_do_something(&readset, &writeset,
957                    &max_fd2, &nalloc, rekeying);
958
959                if (quit_pending)
960                        break;
961
962                /* Do channel operations unless rekeying in progress. */
963                if (!rekeying) {
964                        channel_after_select(readset, writeset);
965
966                        if (need_rekeying) {
967                                debug("user requests rekeying");
968                                xxx_kex->done = 0;
969                                kex_send_kexinit(xxx_kex);
970                                need_rekeying = 0;
971                        }
972                }
973
974                /* Buffer input from the connection.  */
975                client_process_net_input(readset);
976
977                if (quit_pending)
978                        break;
979
980                if (!compat20) {
981                        /* Buffer data from stdin */
982                        client_process_input(readset);
983                        /*
984                         * Process output to stdout and stderr.  Output to
985                         * the connection is processed elsewhere (above).
986                         */
987                        client_process_output(writeset);
988                }
989
990                /* Send as much buffered packet data as possible to the sender. */
991                if (FD_ISSET(connection_out, writeset))
992                        packet_write_poll();
993        }
994        if (readset)
995                xfree(readset);
996        if (writeset)
997                xfree(writeset);
998
999        /* Terminate the session. */
1000
1001        /* Stop watching for window change. */
1002        if (have_pty)
1003                signal(SIGWINCH, SIG_DFL);
1004
1005        channel_free_all();
1006
1007        if (have_pty)
1008                leave_raw_mode();
1009
1010        /* restore blocking io */
1011        if (!isatty(fileno(stdin)))
1012                unset_nonblock(fileno(stdin));
1013        if (!isatty(fileno(stdout)))
1014                unset_nonblock(fileno(stdout));
1015        if (!isatty(fileno(stderr)))
1016                unset_nonblock(fileno(stderr));
1017
1018        if (received_signal) {
1019                if (in_non_blocking_mode)       /* XXX */
1020                        leave_non_blocking();
1021                fatal("Killed by signal %d.", (int) received_signal);
1022        }
1023
1024        /*
1025         * In interactive mode (with pseudo tty) display a message indicating
1026         * that the connection has been closed.
1027         */
1028        if (have_pty && options.log_level != SYSLOG_LEVEL_QUIET) {
1029                snprintf(buf, sizeof buf, "Connection to %.64s closed.\r\n", host);
1030                buffer_append(&stderr_buffer, buf, strlen(buf));
1031        }
1032
1033        /* Output any buffered data for stdout. */
1034        while (buffer_len(&stdout_buffer) > 0) {
1035                len = write(fileno(stdout), buffer_ptr(&stdout_buffer),
1036                    buffer_len(&stdout_buffer));
1037                if (len <= 0) {
1038                        error("Write failed flushing stdout buffer.");
1039                        break;
1040                }
1041                buffer_consume(&stdout_buffer, len);
1042                stdout_bytes += len;
1043        }
1044
1045        /* Output any buffered data for stderr. */
1046        while (buffer_len(&stderr_buffer) > 0) {
1047                len = write(fileno(stderr), buffer_ptr(&stderr_buffer),
1048                    buffer_len(&stderr_buffer));
1049                if (len <= 0) {
1050                        error("Write failed flushing stderr buffer.");
1051                        break;
1052                }
1053                buffer_consume(&stderr_buffer, len);
1054                stderr_bytes += len;
1055        }
1056
1057        /* Clear and free any buffers. */
1058        memset(buf, 0, sizeof(buf));
1059        buffer_free(&stdin_buffer);
1060        buffer_free(&stdout_buffer);
1061        buffer_free(&stderr_buffer);
1062
1063        /* Report bytes transferred, and transfer rates. */
1064        total_time = get_current_time() - start_time;
1065        debug("Transferred: stdin %lu, stdout %lu, stderr %lu bytes in %.1f seconds",
1066            stdin_bytes, stdout_bytes, stderr_bytes, total_time);
1067        if (total_time > 0)
1068                debug("Bytes per second: stdin %.1f, stdout %.1f, stderr %.1f",
1069                    stdin_bytes / total_time, stdout_bytes / total_time,
1070                    stderr_bytes / total_time);
1071
1072        /* Return the exit status of the program. */
1073        debug("Exit status %d", exit_status);
1074        return exit_status;
1075}
1076
1077/*********/
1078
1079static void
1080client_input_stdout_data(int type, u_int32_t seq, void *ctxt)
1081{
1082        u_int data_len;
1083        char *data = packet_get_string(&data_len);
1084        packet_check_eom();
1085        buffer_append(&stdout_buffer, data, data_len);
1086        memset(data, 0, data_len);
1087        xfree(data);
1088}
1089static void
1090client_input_stderr_data(int type, u_int32_t seq, void *ctxt)
1091{
1092        u_int data_len;
1093        char *data = packet_get_string(&data_len);
1094        packet_check_eom();
1095        buffer_append(&stderr_buffer, data, data_len);
1096        memset(data, 0, data_len);
1097        xfree(data);
1098}
1099static void
1100client_input_exit_status(int type, u_int32_t seq, void *ctxt)
1101{
1102        exit_status = packet_get_int();
1103        packet_check_eom();
1104        /* Acknowledge the exit. */
1105        packet_start(SSH_CMSG_EXIT_CONFIRMATION);
1106        packet_send();
1107        /*
1108         * Must wait for packet to be sent since we are
1109         * exiting the loop.
1110         */
1111        packet_write_wait();
1112        /* Flag that we want to exit. */
1113        quit_pending = 1;
1114}
1115
1116static Channel *
1117client_request_forwarded_tcpip(const char *request_type, int rchan)
1118{
1119        Channel *c = NULL;
1120        char *listen_address, *originator_address;
1121        int listen_port, originator_port;
1122        int sock;
1123
1124        /* Get rest of the packet */
1125        listen_address = packet_get_string(NULL);
1126        listen_port = packet_get_int();
1127        originator_address = packet_get_string(NULL);
1128        originator_port = packet_get_int();
1129        packet_check_eom();
1130
1131        debug("client_request_forwarded_tcpip: listen %s port %d, originator %s port %d",
1132            listen_address, listen_port, originator_address, originator_port);
1133
1134        sock = channel_connect_by_listen_address(listen_port);
1135        if (sock < 0) {
1136                xfree(originator_address);
1137                xfree(listen_address);
1138                return NULL;
1139        }
1140        c = channel_new("forwarded-tcpip",
1141            SSH_CHANNEL_CONNECTING, sock, sock, -1,
1142            CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_WINDOW_DEFAULT, 0,
1143            xstrdup(originator_address), 1);
1144        xfree(originator_address);
1145        xfree(listen_address);
1146        return c;
1147}
1148
1149static Channel *
1150client_request_x11(const char *request_type, int rchan)
1151{
1152        Channel *c = NULL;
1153        char *originator;
1154        int originator_port;
1155        int sock;
1156
1157        if (!options.forward_x11) {
1158                error("Warning: ssh server tried X11 forwarding.");
1159                error("Warning: this is probably a break in attempt by a malicious server.");
1160                return NULL;
1161        }
1162        originator = packet_get_string(NULL);
1163        if (datafellows & SSH_BUG_X11FWD) {
1164                debug2("buggy server: x11 request w/o originator_port");
1165                originator_port = 0;
1166        } else {
1167                originator_port = packet_get_int();
1168        }
1169        packet_check_eom();
1170        /* XXX check permission */
1171        debug("client_request_x11: request from %s %d", originator,
1172            originator_port);
1173        xfree(originator);
1174        sock = x11_connect_display();
1175        if (sock < 0)
1176                return NULL;
1177        c = channel_new("x11",
1178            SSH_CHANNEL_X11_OPEN, sock, sock, -1,
1179            CHAN_TCP_WINDOW_DEFAULT, CHAN_X11_PACKET_DEFAULT, 0,
1180            xstrdup("x11"), 1);
1181        c->force_drain = 1;
1182        return c;
1183}
1184
1185static Channel *
1186client_request_agent(const char *request_type, int rchan)
1187{
1188        Channel *c = NULL;
1189        int sock;
1190
1191        if (!options.forward_agent) {
1192                error("Warning: ssh server tried agent forwarding.");
1193                error("Warning: this is probably a break in attempt by a malicious server.");
1194                return NULL;
1195        }
1196        sock =  ssh_get_authentication_socket();
1197        if (sock < 0)
1198                return NULL;
1199        c = channel_new("authentication agent connection",
1200            SSH_CHANNEL_OPEN, sock, sock, -1,
1201            CHAN_X11_WINDOW_DEFAULT, CHAN_TCP_WINDOW_DEFAULT, 0,
1202            xstrdup("authentication agent connection"), 1);
1203        c->force_drain = 1;
1204        return c;
1205}
1206
1207/* XXXX move to generic input handler */
1208static void
1209client_input_channel_open(int type, u_int32_t seq, void *ctxt)
1210{
1211        Channel *c = NULL;
1212        char *ctype;
1213        int rchan;
1214        u_int rmaxpack, rwindow, len;
1215
1216        ctype = packet_get_string(&len);
1217        rchan = packet_get_int();
1218        rwindow = packet_get_int();
1219        rmaxpack = packet_get_int();
1220
1221        debug("client_input_channel_open: ctype %s rchan %d win %d max %d",
1222            ctype, rchan, rwindow, rmaxpack);
1223
1224        if (strcmp(ctype, "forwarded-tcpip") == 0) {
1225                c = client_request_forwarded_tcpip(ctype, rchan);
1226        } else if (strcmp(ctype, "x11") == 0) {
1227                c = client_request_x11(ctype, rchan);
1228        } else if (strcmp(ctype, "auth-agent@openssh.com") == 0) {
1229                c = client_request_agent(ctype, rchan);
1230        }
1231/* XXX duplicate : */
1232        if (c != NULL) {
1233                debug("confirm %s", ctype);
1234                c->remote_id = rchan;
1235                c->remote_window = rwindow;
1236                c->remote_maxpacket = rmaxpack;
1237                if (c->type != SSH_CHANNEL_CONNECTING) {
1238                        packet_start(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION);
1239                        packet_put_int(c->remote_id);
1240                        packet_put_int(c->self);
1241                        packet_put_int(c->local_window);
1242                        packet_put_int(c->local_maxpacket);
1243                        packet_send();
1244                }
1245        } else {
1246                debug("failure %s", ctype);
1247                packet_start(SSH2_MSG_CHANNEL_OPEN_FAILURE);
1248                packet_put_int(rchan);
1249                packet_put_int(SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED);
1250                if (!(datafellows & SSH_BUG_OPENFAILURE)) {
1251                        packet_put_cstring("open failed");
1252                        packet_put_cstring("");
1253                }
1254                packet_send();
1255        }
1256        xfree(ctype);
1257}
1258static void
1259client_input_channel_req(int type, u_int32_t seq, void *ctxt)
1260{
1261        Channel *c = NULL;
1262        int id, reply, success = 0;
1263        char *rtype;
1264
1265        id = packet_get_int();
1266        rtype = packet_get_string(NULL);
1267        reply = packet_get_char();
1268
1269        debug("client_input_channel_req: channel %d rtype %s reply %d",
1270            id, rtype, reply);
1271
1272        if (session_ident == -1) {
1273                error("client_input_channel_req: no channel %d", session_ident);
1274        } else if (id != session_ident) {
1275                error("client_input_channel_req: channel %d: wrong channel: %d",
1276                    session_ident, id);
1277        }
1278        c = channel_lookup(id);
1279        if (c == NULL) {
1280                error("client_input_channel_req: channel %d: unknown channel", id);
1281        } else if (strcmp(rtype, "exit-status") == 0) {
1282                success = 1;
1283                exit_status = packet_get_int();
1284                packet_check_eom();
1285        }
1286        if (reply) {
1287                packet_start(success ?
1288                    SSH2_MSG_CHANNEL_SUCCESS : SSH2_MSG_CHANNEL_FAILURE);
1289                packet_put_int(c->remote_id);
1290                packet_send();
1291        }
1292        xfree(rtype);
1293}
1294static void
1295client_input_global_request(int type, u_int32_t seq, void *ctxt)
1296{
1297        char *rtype;
1298        int want_reply;
1299        int success = 0;
1300
1301        rtype = packet_get_string(NULL);
1302        want_reply = packet_get_char();
1303        debug("client_input_global_request: rtype %s want_reply %d", rtype, want_reply);
1304        if (want_reply) {
1305                packet_start(success ?
1306                    SSH2_MSG_REQUEST_SUCCESS : SSH2_MSG_REQUEST_FAILURE);
1307                packet_send();
1308                packet_write_wait();
1309        }
1310        xfree(rtype);
1311}
1312
1313static void
1314client_init_dispatch_20(void)
1315{
1316        dispatch_init(&dispatch_protocol_error);
1317
1318        dispatch_set(SSH2_MSG_CHANNEL_CLOSE, &channel_input_oclose);
1319        dispatch_set(SSH2_MSG_CHANNEL_DATA, &channel_input_data);
1320        dispatch_set(SSH2_MSG_CHANNEL_EOF, &channel_input_ieof);
1321        dispatch_set(SSH2_MSG_CHANNEL_EXTENDED_DATA, &channel_input_extended_data);
1322        dispatch_set(SSH2_MSG_CHANNEL_OPEN, &client_input_channel_open);
1323        dispatch_set(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION, &channel_input_open_confirmation);
1324        dispatch_set(SSH2_MSG_CHANNEL_OPEN_FAILURE, &channel_input_open_failure);
1325        dispatch_set(SSH2_MSG_CHANNEL_REQUEST, &client_input_channel_req);
1326        dispatch_set(SSH2_MSG_CHANNEL_WINDOW_ADJUST, &channel_input_window_adjust);
1327        dispatch_set(SSH2_MSG_GLOBAL_REQUEST, &client_input_global_request);
1328
1329        /* rekeying */
1330        dispatch_set(SSH2_MSG_KEXINIT, &kex_input_kexinit);
1331
1332        /* global request reply messages */
1333        dispatch_set(SSH2_MSG_REQUEST_FAILURE, &client_global_request_reply);
1334        dispatch_set(SSH2_MSG_REQUEST_SUCCESS, &client_global_request_reply);
1335}
1336static void
1337client_init_dispatch_13(void)
1338{
1339        dispatch_init(NULL);
1340        dispatch_set(SSH_MSG_CHANNEL_CLOSE, &channel_input_close);
1341        dispatch_set(SSH_MSG_CHANNEL_CLOSE_CONFIRMATION, &channel_input_close_confirmation);
1342        dispatch_set(SSH_MSG_CHANNEL_DATA, &channel_input_data);
1343        dispatch_set(SSH_MSG_CHANNEL_OPEN_CONFIRMATION, &channel_input_open_confirmation);
1344        dispatch_set(SSH_MSG_CHANNEL_OPEN_FAILURE, &channel_input_open_failure);
1345        dispatch_set(SSH_MSG_PORT_OPEN, &channel_input_port_open);
1346        dispatch_set(SSH_SMSG_EXITSTATUS, &client_input_exit_status);
1347        dispatch_set(SSH_SMSG_STDERR_DATA, &client_input_stderr_data);
1348        dispatch_set(SSH_SMSG_STDOUT_DATA, &client_input_stdout_data);
1349
1350        dispatch_set(SSH_SMSG_AGENT_OPEN, options.forward_agent ?
1351            &auth_input_open_request : &deny_input_open);
1352        dispatch_set(SSH_SMSG_X11_OPEN, options.forward_x11 ?
1353            &x11_input_open : &deny_input_open);
1354}
1355static void
1356client_init_dispatch_15(void)
1357{
1358        client_init_dispatch_13();
1359        dispatch_set(SSH_MSG_CHANNEL_CLOSE, &channel_input_ieof);
1360        dispatch_set(SSH_MSG_CHANNEL_CLOSE_CONFIRMATION, & channel_input_oclose);
1361}
1362static void
1363client_init_dispatch(void)
1364{
1365        if (compat20)
1366                client_init_dispatch_20();
1367        else if (compat13)
1368                client_init_dispatch_13();
1369        else
1370                client_init_dispatch_15();
1371}
Note: See TracBrowser for help on using the repository browser.