+static void
+process_cmdline(void)
+{
+ void (*handler)(int);
+ char *s, *cmd;
+ u_short fwd_port, fwd_host_port;
+ char buf[1024], sfwd_port[6], sfwd_host_port[6];
+ int local = 0;
+
+ leave_raw_mode();
+ handler = signal(SIGINT, SIG_IGN);
+ cmd = s = read_passphrase("\r\nssh> ", RP_ECHO);
+ if (s == NULL)
+ goto out;
+ while (*s && isspace(*s))
+ s++;
+ if (*s == 0)
+ goto out;
+ if (strlen(s) < 2 || s[0] != '-' || !(s[1] == 'L' || s[1] == 'R')) {
+ logit("Invalid command.");
+ goto out;
+ }
+ if (s[1] == 'L')
+ local = 1;
+ if (!local && !compat20) {
+ logit("Not supported for SSH protocol version 1.");
+ goto out;
+ }
+ s += 2;
+ while (*s && isspace(*s))
+ s++;
+
+ if (sscanf(s, "%5[0-9]:%255[^:]:%5[0-9]",
+ sfwd_port, buf, sfwd_host_port) != 3 &&
+ sscanf(s, "%5[0-9]/%255[^/]/%5[0-9]",
+ sfwd_port, buf, sfwd_host_port) != 3) {
+ logit("Bad forwarding specification.");
+ goto out;
+ }
+ if ((fwd_port = a2port(sfwd_port)) == 0 ||
+ (fwd_host_port = a2port(sfwd_host_port)) == 0) {
+ logit("Bad forwarding port(s).");
+ goto out;
+ }
+ if (local) {
+ if (channel_setup_local_fwd_listener(fwd_port, buf,
+ fwd_host_port, options.gateway_ports) < 0) {
+ logit("Port forwarding failed.");
+ goto out;
+ }
+ } else
+ channel_request_remote_forwarding(fwd_port, buf,
+ fwd_host_port);
+ logit("Forwarding port.");
+out:
+ signal(SIGINT, handler);
+ enter_raw_mode();
+ if (cmd)
+ xfree(cmd);
+}
+
+/* process the characters one by one */
+static int
+process_escapes(Buffer *bin, Buffer *bout, Buffer *berr, char *buf, int len)
+{
+ char string[1024];
+ pid_t pid;
+ int bytes = 0;
+ u_int i;
+ u_char ch;
+ char *s;
+
+ for (i = 0; i < len; i++) {
+ /* Get one character at a time. */
+ ch = buf[i];
+
+ if (escape_pending) {
+ /* We have previously seen an escape character. */
+ /* Clear the flag now. */
+ escape_pending = 0;
+
+ /* Process the escaped character. */
+ switch (ch) {
+ case '.':
+ /* Terminate the connection. */
+ snprintf(string, sizeof string, "%c.\r\n", escape_char);
+ buffer_append(berr, string, strlen(string));
+
+ quit_pending = 1;
+ return -1;
+
+ case 'Z' - 64:
+ /* Suspend the program. */
+ /* Print a message to that effect to the user. */
+ snprintf(string, sizeof string, "%c^Z [suspend ssh]\r\n", escape_char);
+ buffer_append(berr, string, strlen(string));
+
+ /* Restore terminal modes and suspend. */
+ client_suspend_self(bin, bout, berr);
+
+ /* We have been continued. */
+ continue;
+
+ case 'B':
+ if (compat20) {
+ snprintf(string, sizeof string,
+ "%cB\r\n", escape_char);
+ buffer_append(berr, string,
+ strlen(string));
+ channel_request_start(session_ident,
+ "break", 0);
+ packet_put_int(1000);
+ packet_send();
+ }
+ continue;
+
+ case 'R':
+ if (compat20) {
+ if (datafellows & SSH_BUG_NOREKEY)
+ logit("Server does not support re-keying");
+ else
+ need_rekeying = 1;
+ }
+ continue;
+
+ case '&':
+ /*
+ * Detach the program (continue to serve connections,
+ * but put in background and no more new connections).
+ */
+ /* Restore tty modes. */
+ leave_raw_mode();
+
+ /* Stop listening for new connections. */
+ channel_stop_listening();
+
+ snprintf(string, sizeof string,
+ "%c& [backgrounded]\n", escape_char);
+ buffer_append(berr, string, strlen(string));
+
+ /* Fork into background. */
+ pid = fork();
+ if (pid < 0) {
+ error("fork: %.100s", strerror(errno));
+ continue;
+ }
+ if (pid != 0) { /* This is the parent. */
+ /* The parent just exits. */
+ exit(0);
+ }
+ /* The child continues serving connections. */
+ if (compat20) {
+ buffer_append(bin, "\004", 1);
+ /* fake EOF on stdin */
+ return -1;
+ } else if (!stdin_eof) {
+ /*
+ * Sending SSH_CMSG_EOF alone does not always appear
+ * to be enough. So we try to send an EOF character
+ * first.
+ */
+ packet_start(SSH_CMSG_STDIN_DATA);
+ packet_put_string("\004", 1);
+ packet_send();
+ /* Close stdin. */
+ stdin_eof = 1;
+ if (buffer_len(bin) == 0) {
+ packet_start(SSH_CMSG_EOF);
+ packet_send();
+ }
+ }
+ continue;
+
+ case '?':
+ snprintf(string, sizeof string,
+"%c?\r\n\
+Supported escape sequences:\r\n\
+%c. - terminate connection\r\n\
+%cB - send a BREAK to the remote system\r\n\
+%cC - open a command line\r\n\
+%cR - Request rekey (SSH protocol 2 only)\r\n\
+%c^Z - suspend ssh\r\n\
+%c# - list forwarded connections\r\n\
+%c& - background ssh (when waiting for connections to terminate)\r\n\
+%c? - this message\r\n\
+%c%c - send the escape character by typing it twice\r\n\
+(Note that escapes are only recognized immediately after newline.)\r\n",
+ escape_char, escape_char, escape_char, escape_char,
+ escape_char, escape_char, escape_char, escape_char,
+ escape_char, escape_char, escape_char);
+ buffer_append(berr, string, strlen(string));
+ continue;
+
+ case '#':
+ snprintf(string, sizeof string, "%c#\r\n", escape_char);
+ buffer_append(berr, string, strlen(string));
+ s = channel_open_message();
+ buffer_append(berr, s, strlen(s));
+ xfree(s);
+ continue;
+
+ case 'C':
+ process_cmdline();
+ continue;
+
+ default:
+ if (ch != escape_char) {
+ buffer_put_char(bin, escape_char);
+ bytes++;
+ }
+ /* Escaped characters fall through here */
+ break;
+ }
+ } else {
+ /*
+ * The previous character was not an escape char. Check if this
+ * is an escape.
+ */
+ if (last_was_cr && ch == escape_char) {
+ /* It is. Set the flag and continue to next character. */
+ escape_pending = 1;
+ continue;
+ }
+ }
+
+ /*
+ * Normal character. Record whether it was a newline,
+ * and append it to the buffer.
+ */
+ last_was_cr = (ch == '\r' || ch == '\n');
+ buffer_put_char(bin, ch);
+ bytes++;
+ }
+ return bytes;
+}
+
+static void