<commit_before>/*
 * channels.c - SSH channel functions
 *
 * This file is part of the SSH Library
 *
 * Copyright (c) 2003-2008 by <NAME>
 *
 * The SSH Library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or (at your
 * option) any later version.
 *
 * The SSH Library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with the SSH Library; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
 * MA 02111-1307, USA.
 *
 * vim: ts=2 sw=2 et cindent
 */

#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>

#include "libssh/priv.h"
#include "libssh/ssh2.h"

#define WINDOWBASE 128000
#define WINDOWLIMIT (WINDOWBASE/2)

/** \defgroup ssh_channel SSH Channels
 * \brief functions that manage a channel
 */

/** \addtogroup ssh_channel
 * @{ */

/** \brief allocate a new channel
 * \param session ssh session
 * \return An allocated channel, NULL on error.
 */
CHANNEL *channel_new(SSH_SESSION *session) {
  CHANNEL *channel = NULL;

  channel = malloc(sizeof(CHANNEL));
  if (channel == NULL) {
    return NULL;
  }
  memset(channel,0,sizeof(CHANNEL));

  channel->stdout_buffer = buffer_new();
  if (channel->stdout_buffer == NULL) {
    SAFE_FREE(channel);
    return NULL;
  }

  channel->stderr_buffer = buffer_new();
  if (channel->stderr_buffer == NULL) {
    SAFE_FREE(channel);
    return NULL;
  }

  channel->session = session;
  channel->version = session->version;
  channel->exit_status = -1;

  if(session->channels == NULL) {
    session->channels = channel;
    channel->next = channel->prev = channel;
    return channel;
  }
  channel->next = session->channels;
  channel->prev = session->channels->prev;
  channel->next->prev = channel;
  channel->prev->next = channel;

  return channel;
}

u32 ssh_channel_new_id(SSH_SESSION *session){
    u32 ret=session->maxchannel;
    session->maxchannel++;
    return ret;
}

static int channel_open(CHANNEL *channel, const char *type_c, int window,
    int maxpacket, BUFFER *payload) {
    SSH_SESSION *session=channel->session;
    STRING *type=string_from_char(type_c);
    u32 foo;
    int err;
    enter_function();
    buffer_add_u8(session->out_buffer,SSH2_MSG_CHANNEL_OPEN);
    channel->local_channel=ssh_channel_new_id(session);
    channel->local_maxpacket=maxpacket;
    channel->local_window=window;
    ssh_log(session, SSH_LOG_RARE,
        "creating a channel %d with %d window and %d max packet",
        channel->local_channel, window,maxpacket);
    buffer_add_ssh_string(session->out_buffer,type);
    buffer_add_u32(session->out_buffer,htonl(channel->local_channel));
    buffer_add_u32(session->out_buffer,htonl(channel->local_window));
    buffer_add_u32(session->out_buffer,htonl(channel->local_maxpacket));
    free(type);
    if(payload)
        buffer_add_buffer(session->out_buffer,payload);
    packet_send(session);
    ssh_log(session, SSH_LOG_RARE,
        "Sent a SSH_MSG_CHANNEL_OPEN type %s for channel %d",
        type_c, channel->local_channel);
    err=packet_wait(session,SSH2_MSG_CHANNEL_OPEN_CONFIRMATION,1);
    switch(session->in_packet.type){
        case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION:
            buffer_get_u32(session->in_buffer,&foo);
            if(channel->local_channel!=ntohl(foo)){
                ssh_set_error(session,SSH_FATAL,"server answered with sender chan num %d instead of given %d",
                ntohl(foo),channel->local_channel);
                leave_function();
                return -1;
                }
            buffer_get_u32(session->in_buffer,&foo);
            channel->remote_channel=ntohl(foo);
            buffer_get_u32(session->in_buffer,&foo);
            channel->remote_window=ntohl(foo);
            buffer_get_u32(session->in_buffer,&foo);
            channel->remote_maxpacket=ntohl(foo);
            ssh_log(session, SSH_LOG_PROTOCOL,
                "Received a CHANNEL_OPEN_CONFIRMATION for channel %d:%d",
                channel->local_channel,
                channel->remote_channel);
            ssh_log(session, SSH_LOG_PROTOCOL,
                "Remote window : %lu, maxpacket : %lu",
                (long unsigned int) channel->remote_window,
                (long unsigned int) channel->remote_maxpacket);
            channel->open=1;
            leave_function();
            return 0;
        case SSH2_MSG_CHANNEL_OPEN_FAILURE:
            {
                u32 code;
                STRING *error_s;
                char *error;
                buffer_get_u32(session->in_buffer,&foo);
                buffer_get_u32(session->in_buffer,&code);
                error_s=buffer_get_ssh_string(session->in_buffer);
                error=string_to_char(error_s);
                ssh_set_error(session,SSH_REQUEST_DENIED,"Channel opening failure : channel %d error (%d) %s",
                    channel->local_channel,ntohl(code),error);
                free(error);
                free(error_s);
                leave_function();
                return -1;
            }
        default:
            ssh_set_error(session,SSH_FATAL,"Received unknown packet %d\n",session->in_packet.type);
            leave_function();
            return -1;
        }
    leave_function();
    return -1;
}

CHANNEL *ssh_channel_from_local(SSH_SESSION *session,u32 num){
    // we assume we are always the local
    CHANNEL *initchan,*channel;
    initchan=session->channels;
    if(!initchan)
        return NULL;
    for(channel=initchan;channel->local_channel!=num;channel=channel->next){
        if(channel->next==initchan)
            return NULL;
    }
    return channel;
}

static void grow_window(SSH_SESSION *session, CHANNEL *channel, int minimumsize){
    u32 new_window=minimumsize>WINDOWBASE ? minimumsize : WINDOWBASE;
    enter_function();
    buffer_add_u8(session->out_buffer,SSH2_MSG_CHANNEL_WINDOW_ADJUST);
    buffer_add_u32(session->out_buffer,htonl(channel->remote_channel));
    buffer_add_u32(session->out_buffer,htonl(new_window));
    packet_send(session);
    ssh_log(session, SSH_LOG_PROTOCOL,
        "growing window (channel %d:%d) to %d bytes",
        channel->local_channel,
        channel->remote_channel,
        channel->local_window + new_window);
    channel->local_window+=new_window;
    leave_function();
}

static CHANNEL *channel_from_msg(SSH_SESSION *session){
    u32 chan;
    CHANNEL *channel;
    if (buffer_get_u32(session->in_buffer,&chan)!=sizeof(u32)){
        ssh_set_error(session,SSH_FATAL,"Getting channel from message : short read");
        return NULL;
    }
    channel=ssh_channel_from_local(session,ntohl(chan));
    if(!channel)
        ssh_set_error(session,SSH_FATAL,"Server specified invalid channel %d",ntohl(chan));
    return channel;
}

static void channel_rcv_change_window(SSH_SESSION *session){
    u32 bytes;
    CHANNEL *channel;
    int err;
    enter_function();
    channel=channel_from_msg(session);
    if(!channel)
        ssh_log(session, SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session));
    err = buffer_get_u32(session->in_buffer,&bytes);
    if(!channel || err!= sizeof(u32)){
        ssh_log(session, SSH_LOG_PACKET,
            "Error getting a window adjust message : invalid packet");
        leave_function();
        return;
    }
    bytes=ntohl(bytes);
    ssh_log(session, SSH_LOG_PROTOCOL,
        "Adding %d bytes to channel (%d:%d) (from %d bytes)",
        bytes,
        channel->local_channel,
        channel->remote_channel,
        channel->remote_window);
    channel->remote_window+=bytes;
    leave_function();
}

/* is_stderr is set to 1 if the data are extended, ie stderr */
static void channel_rcv_data(SSH_SESSION *session,int is_stderr){
    STRING *str;
    CHANNEL *channel;
    enter_function();
    channel=channel_from_msg(session);
    if(!channel){
        ssh_log(session, SSH_LOG_FUNCTIONS,
            "%s", ssh_get_error(session));
        leave_function();
        return;
    }
    if(is_stderr){
        u32 ignore;
        /* uint32 data type code. we can ignore it */
        buffer_get_u32(session->in_buffer,&ignore);
    }
    str=buffer_get_ssh_string(session->in_buffer);

    if(!str){
        ssh_log(session, SSH_LOG_PACKET, "Invalid data packet!");
        leave_function();
        return;
    }
    ssh_log(session, SSH_LOG_PROTOCOL,
        "Channel receiving %zu bytes data in %d (local win=%d remote win=%d)",
        string_len(str),
        is_stderr,
        channel->local_window,
        channel->remote_window);
    /* what shall we do in this case ? let's accept it anyway */
    if(string_len(str)>channel->local_window)
        ssh_log(session, SSH_LOG_RARE,
            "Data packet too big for our window(%zu vs %d)",
            string_len(str),
            channel->local_window);
    if (channel_default_bufferize(channel,str->string,string_len(str), is_stderr) < 0) {
      string_free(str);
      leave_function();
      return;
    }
    if(string_len(str)<=channel->local_window)
        channel->local_window-=string_len(str);
    else
        channel->local_window=0; /* buggy remote */
    ssh_log(session, SSH_LOG_PROTOCOL,
        "Channel windows are now (local win=%d remote win=%d)",
        channel->local_window,
        channel->remote_window);
    free(str);
    leave_function();
}

static void channel_rcv_eof(SSH_SESSION *session){
    CHANNEL *channel;
    enter_function();
    channel=channel_from_msg(session);
    if(!channel){
        ssh_log(session, SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session));
        leave_function();
        return;
    }
    ssh_log(session, SSH_LOG_PACKET,
        "Received eof on channel (%d:%d)",
        channel->local_channel,
        channel->remote_channel);
//    channel->remote_window=0;
    channel->remote_eof=1;
    leave_function();
}

static void channel_rcv_close(SSH_SESSION *session){
    CHANNEL *channel;
    enter_function();
    channel=channel_from_msg(session);
    if(!channel){
        ssh_log(session, SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session));
        leave_function();
        return;
    }
    ssh_log(session, SSH_LOG_PACKET,
        "Received close on channel (%d:%d)",
        channel->local_channel,
        channel->remote_channel);
    if((channel->stdout_buffer && buffer_get_rest_len(channel->stdout_buffer)>0)
            || (channel->stderr_buffer && buffer_get_rest_len(channel->stderr_buffer)>0))
        channel->delayed_close=1;
    else
        channel->open=0;
    if(!channel->remote_eof)
        ssh_log(session, SSH_LOG_PACKET,
            "Remote host not polite enough to send an eof before close");
    channel->remote_eof=1;
    /* the remote eof doesn't break things if there was still data into read
     * buffer because the eof is ignored until the buffer is empty. */
    leave_function();
}

static void channel_rcv_request(SSH_SESSION *session){
    STRING *request_s;
    char *request;
    u32 status;
    CHANNEL *channel=channel_from_msg(session);
    enter_function();
    if(!channel){
        ssh_log(session, SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session));
        leave_function();
        return;
    }
    request_s=buffer_get_ssh_string(session->in_buffer);
    if(!request_s){
        ssh_log(session, SSH_LOG_PACKET, "Invalid MSG_CHANNEL_REQUEST");
        leave_function();
        return;
    }
    buffer_get_u8(session->in_buffer,(u8 *)&status);
    request=string_to_char(request_s);
    if(!strcmp(request,"exit-status")){
        ssh_log(session, SSH_LOG_PACKET, "received exit-status");
        buffer_get_u32(session->in_buffer,&status);
        status=ntohl(status);
        channel->exit_status=status;
/* TODO do something with status, we might need it */
        free(request_s);
        free(request);
        leave_function();
        return ;
    }
    if(!strcmp(request,"exit-signal")){
        STRING *signal_s;
        char *signal;
        const char *core = "(core dumped)";
        u8 i;
        signal_s=buffer_get_ssh_string(session->in_buffer);
        if(!signal_s){
            ssh_log(session, SSH_LOG_PACKET, "Invalid MSG_CHANNEL_REQUEST");
            free(request_s);
            free(request);
            leave_function();
            return;
        }
        signal=string_to_char(signal_s);
        buffer_get_u8(session->in_buffer,&i);
        if(!i)
            core="";
        ssh_log(session, SSH_LOG_PACKET,
            "Remote connection closed by signal SIG %s %s", signal, core);
        free(signal_s);
        free(signal);
        free(request_s);
        free(request);
        leave_function();
        return;
    }
    ssh_log(session, SSH_LOG_PACKET, "Unknown request %s", request);
    free(request_s);
    free(request);
    leave_function();
}

/* channel_handle is called by wait_packet, ie, when there is channel informations to handle . */
void channel_handle(SSH_SESSION *session, int type){
    enter_function();
    ssh_log(session, SSH_LOG_PROTOCOL, "Channel_handle(%d)", type);
    switch(type){
        case SSH2_MSG_CHANNEL_WINDOW_ADJUST:
            channel_rcv_change_window(session);
            break;
        case SSH2_MSG_CHANNEL_DATA:
            channel_rcv_data(session,0);
            break;
        case SSH2_MSG_CHANNEL_EXTENDED_DATA:
            channel_rcv_data(session,1);
            break;
        case SSH2_MSG_CHANNEL_EOF:
            channel_rcv_eof(session);
            break;
        case SSH2_MSG_CHANNEL_CLOSE:
            channel_rcv_close(session);
            break;
        case SSH2_MSG_CHANNEL_REQUEST:
            channel_rcv_request(session);
            break;
        default:
            ssh_log(session, SSH_LOG_FUNCTIONS,
                "Unexpected message %d", type);
        }
    leave_function();
}

/* when data has been received from the ssh server, it can be applied to the known
    user function, with help of the callback, or inserted here */
/* XXX is the window changed ? */
int channel_default_bufferize(CHANNEL *channel, void *data, int len,
    int is_stderr) {
  struct ssh_session *session = channel->session;

  ssh_log(session, SSH_LOG_RARE,
      "placing %d bytes into channel buffer (stderr=%d)", len, is_stderr);
  if (! is_stderr) {
    /* stdout */
    if(channel->stdout_buffer == NULL) {
      channel->stdout_buffer = buffer_new();
      if (channel->stdout_buffer == NULL) {
        return -1;
      }
    }

    if (buffer_add_data(channel->stdout_buffer, data, len) < 0) {
      buffer_free(channel->stdout_buffer);
      return -1;
    }
  } else {
    /* stderr */
    if (channel->stderr_buffer == NULL) {
      channel->stderr_buffer = buffer_new();
      if (channel->stderr_buffer == NULL) {
        return -1;
      }
    }

    if (buffer_add_data(channel->stderr_buffer, data, len) < 0) {
      buffer_free(channel->stderr_buffer);
      return -1;
    }
  }

  return 0;
}

/** \brief open a session channel (suited for a shell. Not tcp Forwarding)
 * \param channel an allocated channel (see channel_new())
 * \return SSH_OK on success\n
 * SSH_ERROR on error
 * \see channel_open_forward()
 * \see channel_request_env()
 * \see channel_request_shell()
 * \see channel_request_exec()
 * \warning API changed from 0.11
 */
int channel_open_session(CHANNEL *channel){
#ifdef HAVE_SSH1
    if(channel->session->version==2)
#endif
        return channel_open(channel,"session",64000,32000,NULL);
#ifdef HAVE_SSH1
    else
        return channel_open_session1(channel);
#endif
}

/** \brief open a TCP/IP forwarding channel.
 * \param channel an allocated channel (see channel_new())
 * \param remotehost remote host to be connected (host name or IP)
 * \param remoteport remote port
 * \param sourcehost source host (your local computer). It's facultative and for logging purpose
 * \param localport source port (your local computer). It's facultative and for logging purpose
 * \return SSH_ERROR on error\n
 * SSH_OK on success
 * \warning API changed from 0.11
 */
int channel_open_forward(CHANNEL *channel, const char *remotehost,
    int remoteport, const char *sourcehost, int localport) {
    BUFFER *payload=buffer_new();
    STRING *str=string_from_char(remotehost);
    SSH_SESSION *session=channel->session;
    int ret;
    enter_function();
    buffer_add_ssh_string(payload,str);
    free(str);
    str=string_from_char(sourcehost);
    buffer_add_u32(payload,htonl(remoteport));
    buffer_add_ssh_string(payload,str);
    free(str);
    buffer_add_u32(payload,htonl(localport));
    ret=channel_open(channel,"direct-tcpip",64000,32000,payload);
    buffer_free(payload);
    leave_function();
    return ret;
}

/** \brief close and free a channel
 * \param channel channel to free
 * \warning any data unread on channel will be lost
 */
void channel_free(CHANNEL *channel) {
  SSH_SESSION *session = channel->session;
  enter_function();

  if (channel == NULL) {
    leave_function();
    return;
  }

  if (session->alive && channel->open) {
    channel_close(channel);
  }

  /* handle the "my channel is first on session list" case */
  if (session->channels == channel) {
    session->channels = channel->next;
  }

  /* handle the "my channel is the only on session list" case */
  if (channel->next == channel) {
    session->channels = NULL;
  } else {
    channel->prev->next = channel->next;
    channel->next->prev = channel->prev;
  }

  buffer_free(channel->stdout_buffer);
  buffer_free(channel->stderr_buffer);

  /* debug trick to catch use after frees */
  memset(channel, 'X', sizeof(CHANNEL));
  SAFE_FREE(channel);
  leave_function();
}

/** it doesn't close the channel. You may still read from it but not write.
 * \brief send an end of file on the channel
 * \param channel channel
 * \return SSH_ERROR on error\n
 * SSH_SUCCESS on success
 * \see channel_close()
 * \see channel_free()
 */
int channel_send_eof(CHANNEL *channel){
    SSH_SESSION *session=channel->session;
    int ret;
    enter_function();
    buffer_add_u8(session->out_buffer,SSH2_MSG_CHANNEL_EOF);
    buffer_add_u32(session->out_buffer,htonl(channel->remote_channel));
    ret=packet_send(session);
    ssh_log(session, SSH_LOG_PACKET,
        "Sent a EOF on client channel (%d:%d)",
        channel->local_channel,
        channel->remote_channel);
    channel->local_eof=1;
    leave_function();
    return ret;
}

/** It sends an end of file and then closes the channel. You won't be able
 * to recover any data the server was going to send or was in buffers.
 * \brief close a channel
 * \param channel channel
 * \return SSH_ERROR on error\n
 * SSH_SUCCESS on success
 * \see channel_free()
 * \see channel_eof()
 */
int channel_close(CHANNEL *channel){
    SSH_SESSION *session=channel->session;
    int ret=0;
    enter_function();
    if(!channel->local_eof)
        ret=channel_send_eof(channel);
    if(ret){
        leave_function();
    	return ret;
    }
    buffer_add_u8(session->out_buffer,SSH2_MSG_CHANNEL_CLOSE);
    buffer_add_u32(session->out_buffer,htonl(channel->remote_channel));
    ret=packet_send(session);
    ssh_log(session, SSH_LOG_PACKET,
        "Sent a close on client channel (%d:%d)",
        channel->local_channel,
        channel->remote_channel);
    if(!ret)
        channel->open =0;
    leave_function();
    return ret;
}

/** \brief blocking write on channel
 * \param channel channel
 * \param data pointer to data to write
 * \param len length of data
 * \return number of bytes written on success\n
 * SSH_ERROR on error
 * \see channel_read()
 */
int channel_write(CHANNEL *channel, const void *data, u32 len) {
    SSH_SESSION *session=channel->session;
    int effectivelen;
    int origlen=len;
    enter_function();
    if(channel->local_eof){
        ssh_set_error(session,SSH_REQUEST_DENIED,"Can't write to channel %d:%d"
            " after EOF was sent",channel->local_channel,channel->remote_channel);
        leave_function();
        return -1;
    }
    if(!channel->open || channel->delayed_close){
        ssh_set_error(session,SSH_REQUEST_DENIED,"Remote channel is closed");
        leave_function();
        return -1;
    }
#ifdef HAVE_SSH1
    if(channel->version==1){
        int err = channel_write1(channel,data,len);
        leave_function();
        return err;
    }
#endif
    while(len >0){
        if(channel->remote_window<len){
            ssh_log(session, SSH_LOG_PROTOCOL,
                "Remote window is %d bytes. going to write %d bytes",
                channel->remote_window,len);
            ssh_log(session, SSH_LOG_PROTOCOL,
                "Waiting for a growing window message...");
            // wonder what happens when the channel window is zero
            while(channel->remote_window==0){
                // parse every incoming packet
                packet_wait(channel->session,0,0);
            }
            effectivelen=len>channel->remote_window?channel->remote_window:len;
        } else
            effectivelen=len;
        buffer_add_u8(session->out_buffer,SSH2_MSG_CHANNEL_DATA);
        buffer_add_u32(session->out_buffer,htonl(channel->remote_channel));
        buffer_add_u32(session->out_buffer,htonl(effectivelen));
        buffer_add_data(session->out_buffer,data,effectivelen);
        packet_send(session);
        ssh_log(session, SSH_LOG_RARE,
            "channel_write wrote %d bytes", effectivelen);
        channel->remote_window-=effectivelen;
        len -= effectivelen;
        data+=effectivelen;
    }
    leave_function();
    return origlen;
}

/** \brief returns if the channel is open or not
 * \param channel channel
 * \return 0 if channel is closed, nonzero otherwise
 * \see channel_is_closed()
 */
int channel_is_open(CHANNEL *channel){
    return (channel->open!=0 && channel->session->alive);
}

/** \brief returns if the channel is closed or not
 * \param channel channel
 * \return 0 if channel is opened, nonzero otherwise
 * \see channel_is_open()
 */

int channel_is_closed(CHANNEL *channel){
    return (channel->open==0 || !channel->session->alive);
}

/** \brief returns if the remote has sent an EOF
 * \param channel channel
 * \return 0 if there is no EOF, nonzero otherwise
 */
int channel_is_eof(CHANNEL *channel){
    if((channel->stdout_buffer && buffer_get_rest_len(channel->stdout_buffer)
                >0) || (channel->stderr_buffer && buffer_get_rest_len(
                    channel->stderr_buffer)>0))
        return 0;
    return (channel->remote_eof!=0);
}

/** \brief put the channel into nonblocking mode
 * \param channel channel
 * \param blocking boolean for blocking or nonblocking
 * \bug This functionnality is still under development and
 * doesn't work correctly
 */
void channel_set_blocking(CHANNEL *channel, int blocking){
    channel->blocking=blocking;
}

static int channel_request(CHANNEL *channel, const char *request,
    BUFFER *buffer, int reply) {
    STRING *request_s=string_from_char(request);
    SSH_SESSION *session=channel->session;
    int err;
    enter_function();
    buffer_add_u8(session->out_buffer,SSH2_MSG_CHANNEL_REQUEST);
    buffer_add_u32(session->out_buffer,htonl(channel->remote_channel));
    buffer_add_ssh_string(session->out_buffer,request_s);
    buffer_add_u8(session->out_buffer,reply?1:0);
    if(buffer)
        buffer_add_data(session->out_buffer,buffer_get(buffer),buffer_get_len(buffer));
    packet_send(session);
    ssh_log(session, SSH_LOG_RARE,
        "Sent a SSH_MSG_CHANNEL_REQUEST %s", request);
    free(request_s);
    if(!reply){
    	leave_function();
    	return 0;
    }
    err=packet_wait(session,SSH2_MSG_CHANNEL_SUCCESS,1);
    if(err)
        if(session->in_packet.type==SSH2_MSG_CHANNEL_FAILURE){
            ssh_log(session, SSH_LOG_PACKET,
                "%s channel request failed", request);
            ssh_set_error(session,SSH_REQUEST_DENIED,"Channel request %s failed",request);
        }
        else
            ssh_log(session, SSH_LOG_RARE,
                "Received an unexpected %d message", session->in_packet.type);
    else
        ssh_log(session, SSH_LOG_RARE, "Received a SUCCESS");
    leave_function();
    return err;
}

/** \brief requests a pty with a specific type and size
 * \param channel channel
 * \param terminal terminal type ("vt100, xterm,...")
 * \param col number of cols
 * \param row number of rows
 * \return SSH_SUCCESS on success\n
 * SSH_ERROR on error
 */
int channel_request_pty_size(CHANNEL *channel, const char *terminal,
    int col, int row) {
    STRING *term;
    BUFFER *buffer;
    SSH_SESSION *session=channel->session;
    int err;
    enter_function();
#ifdef HAVE_SSH1
    if(channel->version==1){
        err = channel_request_pty_size1(channel,terminal, col, row);
        leave_function();
        return err;
    }
#endif
    term=string_from_char(terminal);
    buffer=buffer_new();
    buffer_add_ssh_string(buffer,term);
    buffer_add_u32(buffer,htonl(col));
    buffer_add_u32(buffer,htonl(row));
    buffer_add_u32(buffer,0);
    buffer_add_u32(buffer,0);
/* a 0byte string */
    buffer_add_u32(buffer,htonl(1));
    buffer_add_u8(buffer,0);
    free(term);
    err=channel_request(channel,"pty-req",buffer,1);
    buffer_free(buffer);
    leave_function();
    return err;
}

/** \brief requests a pty
 * \param channel channel
 * \see channel_request_pty_size()
 * \return SSH_SUCCESS on success\n
 * SSH_ERROR on error
 */
int channel_request_pty(CHANNEL *channel){
    return channel_request_pty_size(channel,"xterm",80,24);
}

/** \brief change the size of the terminal associated to a channel
 * \param channel channel
 * \param cols new number of cols
 * \param rows new number of rows
 * \warning Do not call it from a signal handler if you are not
 * sure any other libssh function using the same channel/session
 * is running at same time (not 100% threadsafe)
 */
int channel_change_pty_size(CHANNEL *channel,int cols,int rows){
    BUFFER *buffer;
    int err;
    SSH_SESSION *session=channel->session;
    enter_function();
#ifdef HAVE_SSH1
    if(channel->version==1){
        err = channel_change_pty_size1(channel,cols,rows);
        leave_function();
        return err;
    }
#endif
    buffer=buffer_new();
    //buffer_add_u8(buffer,0);
    buffer_add_u32(buffer,htonl(cols));
    buffer_add_u32(buffer,htonl(rows));
    buffer_add_u32(buffer,0);
    buffer_add_u32(buffer,0);
    err=channel_request(channel,"window-change",buffer,0);
    buffer_free(buffer);
    leave_function();
    return err;
}

/** \brief requests a shell
 * \param channel
 * \returns SSH_SUCCESS on success\n
 * SSH_ERROR on error
 */
int channel_request_shell(CHANNEL *channel){
#ifdef HAVE_SSH1
    if(channel->version==1)
        return channel_request_shell1(channel);
#endif
    return channel_request(channel,"shell",NULL,1);
}

/** \brief requests a subsystem (for example sftp)
 * \param channel channel
 * \param system subsystem to request (for example sftp)
 * \return SSH_SUCCESS on success\n
 * SSH_ERROR on error
 * \warning you normally don't have to call it to have sftp
 * \see sftp_new()
 */
int channel_request_subsystem(CHANNEL *channel, const char *sys){
    BUFFER* buffer=buffer_new();
    int ret;
    STRING *subsystem=string_from_char(sys);
    buffer_add_ssh_string(buffer,subsystem);
    free(subsystem);
    ret=channel_request(channel,"subsystem",buffer,1);
    buffer_free(buffer);
    return ret;
}

int channel_request_sftp( CHANNEL *channel){
    return channel_request_subsystem(channel, "sftp");
}

/** \brief set the environement variables
 * \param channel channel
 * \param name name of the variable
 * \param value value
 * \return SSH_SUCCESS on success\n
 * SSH_ERROR on error
 * \warning some environement variables may be refused by security
 * */
int channel_request_env(CHANNEL *channel, const char *name, const char *value){
    BUFFER *buffer=buffer_new();
    int ret;
    STRING *string=string_from_char(name);
    buffer_add_ssh_string(buffer,string);
    free(string);
    string=string_from_char(value);
    buffer_add_ssh_string(buffer,string);
    free(string);
    ret=channel_request(channel,"env",buffer,1);
    buffer_free(buffer);
    return ret;
}

/** it's similar to sh -c "command"
 * \brief run a shell command without an interactive shell
 * \param channel channel
 * \param cmd command to execute (by ex. "ls ~/ -al | grep -i reports")
 * \return SSH_SUCCESS on success\n
 * SSH_ERROR on error
 * \see channel_request_shell()
 */
int channel_request_exec(CHANNEL *channel, const char *cmd) {
    BUFFER *buffer;
    STRING *command;
    int ret;
#ifdef HAVE_SSH1
    if(channel->version==1)
        return channel_request_exec1(channel, cmd);
#endif
    buffer=buffer_new();
    command=string_from_char(cmd);
    buffer_add_ssh_string(buffer,command);
    free(command);
    ret=channel_request(channel,"exec",buffer,1);
    buffer_free(buffer);
    return ret;
}

/* TODO : fix the delayed close thing */
/* TODO : fix the blocking behaviours */
/* reads into a channel and put result into buffer */
/* returns number of bytes read, 0 if eof or such and -1 in case of error */
/* if bytes != 0, the exact number of bytes are going to be read */
/** \brief reads data from a channel
 * \param channel channel
 * \param buffer buffer which will get the data
 * \param bytes number of bytes to be read. If it is bigger
 * than 0, the exact size will be read, else (bytes=0) it will return
 * once anything is available
 * \param is_stderr boolean value to mark reading from the stderr flow.
 * \return number of bytes read\n
 * 0 on end of file\n
 * SSH_ERROR on error
 */
int channel_read(CHANNEL *channel, BUFFER *buffer, u32 bytes, int is_stderr) {
    BUFFER *stdbuf=NULL;
    SSH_SESSION *session=channel->session;
    u32 maxread=bytes;
    u32 len;

    buffer_reinit(buffer);
    enter_function();
    if(bytes==0)
        maxread=MAX_PACKET_LEN;
    /* maybe i should always set a buffer to avoid races between channel_default_bufferize and channel_read */
    if(is_stderr)
        stdbuf=channel->stderr_buffer;
    else
        stdbuf=channel->stdout_buffer;

    /* We may have problem if the window is too small to accept as much data as asked */
    ssh_log(session, SSH_LOG_PROTOCOL,
        "Read (%d) buffered : %d bytes. Window: %d",
        bytes,
        buffer_get_rest_len(stdbuf),
        channel->local_window);
    if(bytes > buffer_get_rest_len(stdbuf) + channel->local_window)
    	grow_window(session,channel,bytes - buffer_get_rest_len(stdbuf));
    /* block reading if asked bytes=0 */
    while((buffer_get_rest_len(stdbuf)==0) || (buffer_get_rest_len(stdbuf) < bytes)){
        if(channel->remote_eof && buffer_get_rest_len(stdbuf)==0){
            leave_function();
        	return 0;
        }
        if(channel->remote_eof)
            break; /* return the resting bytes in buffer */
        if(buffer_get_rest_len(stdbuf)>=maxread) // stop reading when buffer is full enough
            break;
        if(packet_read(session)||packet_translate(session)){
            leave_function();
        	return -1;
        }
        packet_parse(session);
    }
    if(channel->local_window < WINDOWLIMIT)
    	grow_window(session,channel,0);
    if(bytes==0){
        /* write the ful buffer informations */
        buffer_add_data(buffer,buffer_get_rest(stdbuf),buffer_get_rest_len(stdbuf));
        buffer_reinit(stdbuf);
    } else {
        len=buffer_get_rest_len(stdbuf);
        len= (len>bytes?bytes:len); /* read bytes bytes if len is greater, everything otherwise */
        buffer_add_data(buffer,buffer_get_rest(stdbuf),len);
        buffer_pass_bytes(stdbuf,len);
    }
    leave_function();
    return buffer_get_len(buffer);
}

/** \brief polls the channel for data to read
 * \param channel channel
 * \param is_stderr boolean to select the stderr stream
 * \return number of bytes available for reading\n
 * 0 if nothing is available\n
 * SSH_ERROR on error
 * \warning When the channel is in EOF state, the function returns 1
 * \see channel_is_eof()
 */
int channel_poll(CHANNEL *channel, int is_stderr){<commit_msg>Check for buffer_add_* return values in channel functions.<commit_after>/*
 * channels.c - SSH channel functions
 *
 * This file is part of the SSH Library
 *
 * Copyright (c) 2003-2008 by <NAME>
 *
 * The SSH Library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or (at your
 * option) any later version.
 *
 * The SSH Library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with the SSH Library; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
 * MA 02111-1307, USA.
 *
 * vim: ts=2 sw=2 et cindent
 */

#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>

#include "libssh/priv.h"
#include "libssh/ssh2.h"

#define WINDOWBASE 128000
#define WINDOWLIMIT (WINDOWBASE/2)

/** \defgroup ssh_channel SSH Channels
 * \brief functions that manage a channel
 */

/** \addtogroup ssh_channel
 * @{ */

/** \brief allocate a new channel
 * \param session ssh session
 * \return An allocated channel, NULL on error.
 */
CHANNEL *channel_new(SSH_SESSION *session) {
  CHANNEL *channel = NULL;

  channel = malloc(sizeof(CHANNEL));
  if (channel == NULL) {
    return NULL;
  }
  memset(channel,0,sizeof(CHANNEL));

  channel->stdout_buffer = buffer_new();
  if (channel->stdout_buffer == NULL) {
    SAFE_FREE(channel);
    return NULL;
  }

  channel->stderr_buffer = buffer_new();
  if (channel->stderr_buffer == NULL) {
    SAFE_FREE(channel);
    return NULL;
  }

  channel->session = session;
  channel->version = session->version;
  channel->exit_status = -1;

  if(session->channels == NULL) {
    session->channels = channel;
    channel->next = channel->prev = channel;
    return channel;
  }
  channel->next = session->channels;
  channel->prev = session->channels->prev;
  channel->next->prev = channel;
  channel->prev->next = channel;

  return channel;
}

u32 ssh_channel_new_id(SSH_SESSION *session){
    u32 ret=session->maxchannel;
    session->maxchannel++;
    return ret;
}

static int channel_open(CHANNEL *channel, const char *type_c, int window,
    int maxpacket, BUFFER *payload) {
    SSH_SESSION *session=channel->session;
    STRING *type = NULL;
    u32 foo;
    int err;
    enter_function();

    channel->local_channel=ssh_channel_new_id(session);
    channel->local_maxpacket=maxpacket;
    channel->local_window=window;
    ssh_log(session, SSH_LOG_RARE,
        "creating a channel %d with %d window and %d max packet",
        channel->local_channel, window,maxpacket);
    type = string_from_char(type_c);
    if (type == NULL) {
      goto error;
    }

    if (buffer_add_u8(session->out_buffer, SSH2_MSG_CHANNEL_OPEN) < 0) {
      goto error;
    }
    if (buffer_add_ssh_string(session->out_buffer,type) < 0) {
      goto error;
    }
    if (buffer_add_u32(session->out_buffer, htonl(channel->local_channel)) < 0) {
      goto error;
    }
    if (buffer_add_u32(session->out_buffer, htonl(channel->local_window)) < 0) {
      goto error;
    }
    if (buffer_add_u32(session->out_buffer, htonl(channel->local_maxpacket)) < 0) {
      goto error;
    }

    string_free(type);

    if (payload) {
      if (buffer_add_buffer(session->out_buffer, payload) < 0) {
        goto error;
      }
    }
    if (packet_send(session) != SSH_OK) {
      leave_function();
      return -1;
    }

    ssh_log(session, SSH_LOG_RARE,
        "Sent a SSH_MSG_CHANNEL_OPEN type %s for channel %d",
        type_c, channel->local_channel);
    err=packet_wait(session,SSH2_MSG_CHANNEL_OPEN_CONFIRMATION,1);
    switch(session->in_packet.type){
        case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION:
            buffer_get_u32(session->in_buffer,&foo);
            if(channel->local_channel!=ntohl(foo)){
                ssh_set_error(session,SSH_FATAL,"server answered with sender chan num %d instead of given %d",
                ntohl(foo),channel->local_channel);
                leave_function();
                return -1;
                }
            buffer_get_u32(session->in_buffer,&foo);
            channel->remote_channel=ntohl(foo);
            buffer_get_u32(session->in_buffer,&foo);
            channel->remote_window=ntohl(foo);
            buffer_get_u32(session->in_buffer,&foo);
            channel->remote_maxpacket=ntohl(foo);
            ssh_log(session, SSH_LOG_PROTOCOL,
                "Received a CHANNEL_OPEN_CONFIRMATION for channel %d:%d",
                channel->local_channel,
                channel->remote_channel);
            ssh_log(session, SSH_LOG_PROTOCOL,
                "Remote window : %lu, maxpacket : %lu",
                (long unsigned int) channel->remote_window,
                (long unsigned int) channel->remote_maxpacket);
            channel->open=1;
            leave_function();
            return 0;
        case SSH2_MSG_CHANNEL_OPEN_FAILURE:
            {
                u32 code;
                STRING *error_s;
                char *error;
                buffer_get_u32(session->in_buffer,&foo);
                buffer_get_u32(session->in_buffer,&code);
                error_s=buffer_get_ssh_string(session->in_buffer);
                error=string_to_char(error_s);
                ssh_set_error(session,SSH_REQUEST_DENIED,"Channel opening failure : channel %d error (%d) %s",
                    channel->local_channel,ntohl(code),error);
                free(error);
                free(error_s);
                leave_function();
                return -1;
            }
        default:
            ssh_set_error(session,SSH_FATAL,"Received unknown packet %d\n",session->in_packet.type);
            leave_function();
            return -1;
        }

error:
    buffer_free(session->out_buffer);
    string_free(type);

    leave_function();
    return -1;
}

CHANNEL *ssh_channel_from_local(SSH_SESSION *session,u32 num){
    // we assume we are always the local
    CHANNEL *initchan,*channel;
    initchan=session->channels;
    if(!initchan)
        return NULL;
    for(channel=initchan;channel->local_channel!=num;channel=channel->next){
        if(channel->next==initchan)
            return NULL;
    }
    return channel;
}

static int grow_window(SSH_SESSION *session, CHANNEL *channel, int minimumsize) {
  u32 new_window = minimumsize > WINDOWBASE ? minimumsize : WINDOWBASE;

  enter_function();

  if (buffer_add_u8(session->out_buffer,SSH2_MSG_CHANNEL_WINDOW_ADJUST) < 0) {
    goto error;
  }
  if (buffer_add_u32(session->out_buffer,htonl(channel->remote_channel)) < 0) {
    goto error;
  }
  if (buffer_add_u32(session->out_buffer,htonl(new_window)) < 0) {
    goto error;
  }

  if (packet_send(session) != SSH_OK) {
    /* FIXME should we fail here or not? */
    leave_function();
    return 1;
  }
  ssh_log(session, SSH_LOG_PROTOCOL,
      "growing window (channel %d:%d) to %d bytes",
      channel->local_channel,
      channel->remote_channel,
      channel->local_window + new_window);
  channel->local_window+=new_window;
  leave_function();

  return 0;
error:
  buffer_free(session->out_buffer);

  leave_function();
  return -1;
}

static CHANNEL *channel_from_msg(SSH_SESSION *session){
    u32 chan;
    CHANNEL *channel;
    if (buffer_get_u32(session->in_buffer,&chan)!=sizeof(u32)){
        ssh_set_error(session,SSH_FATAL,"Getting channel from message : short read");
        return NULL;
    }
    channel=ssh_channel_from_local(session,ntohl(chan));
    if(!channel)
        ssh_set_error(session,SSH_FATAL,"Server specified invalid channel %d",ntohl(chan));
    return channel;
}

static void channel_rcv_change_window(SSH_SESSION *session){
    u32 bytes;
    CHANNEL *channel;
    int err;
    enter_function();
    channel=channel_from_msg(session);
    if(!channel)
        ssh_log(session, SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session));
    err = buffer_get_u32(session->in_buffer,&bytes);
    if(!channel || err!= sizeof(u32)){
        ssh_log(session, SSH_LOG_PACKET,
            "Error getting a window adjust message : invalid packet");
        leave_function();
        return;
    }
    bytes=ntohl(bytes);
    ssh_log(session, SSH_LOG_PROTOCOL,
        "Adding %d bytes to channel (%d:%d) (from %d bytes)",
        bytes,
        channel->local_channel,
        channel->remote_channel,
        channel->remote_window);
    channel->remote_window+=bytes;
    leave_function();
}

/* is_stderr is set to 1 if the data are extended, ie stderr */
static void channel_rcv_data(SSH_SESSION *session,int is_stderr){
    STRING *str;
    CHANNEL *channel;
    enter_function();
    channel=channel_from_msg(session);
    if(!channel){
        ssh_log(session, SSH_LOG_FUNCTIONS,
            "%s", ssh_get_error(session));
        leave_function();
        return;
    }
    if(is_stderr){
        u32 ignore;
        /* uint32 data type code. we can ignore it */
        buffer_get_u32(session->in_buffer,&ignore);
    }
    str=buffer_get_ssh_string(session->in_buffer);

    if(!str){
        ssh_log(session, SSH_LOG_PACKET, "Invalid data packet!");
        leave_function();
        return;
    }
    ssh_log(session, SSH_LOG_PROTOCOL,
        "Channel receiving %zu bytes data in %d (local win=%d remote win=%d)",
        string_len(str),
        is_stderr,
        channel->local_window,
        channel->remote_window);
    /* what shall we do in this case ? let's accept it anyway */
    if(string_len(str)>channel->local_window)
        ssh_log(session, SSH_LOG_RARE,
            "Data packet too big for our window(%zu vs %d)",
            string_len(str),
            channel->local_window);
    if (channel_default_bufferize(channel,str->string,string_len(str), is_stderr) < 0) {
      string_free(str);
      leave_function();
      return;
    }
    if(string_len(str)<=channel->local_window)
        channel->local_window-=string_len(str);
    else
        channel->local_window=0; /* buggy remote */
    ssh_log(session, SSH_LOG_PROTOCOL,
        "Channel windows are now (local win=%d remote win=%d)",
        channel->local_window,
        channel->remote_window);
    free(str);
    leave_function();
}

static void channel_rcv_eof(SSH_SESSION *session){
    CHANNEL *channel;
    enter_function();
    channel=channel_from_msg(session);
    if(!channel){
        ssh_log(session, SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session));
        leave_function();
        return;
    }
    ssh_log(session, SSH_LOG_PACKET,
        "Received eof on channel (%d:%d)",
        channel->local_channel,
        channel->remote_channel);
//    channel->remote_window=0;
    channel->remote_eof=1;
    leave_function();
}

static void channel_rcv_close(SSH_SESSION *session){
    CHANNEL *channel;
    enter_function();
    channel=channel_from_msg(session);
    if(!channel){
        ssh_log(session, SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session));
        leave_function();
        return;
    }
    ssh_log(session, SSH_LOG_PACKET,
        "Received close on channel (%d:%d)",
        channel->local_channel,
        channel->remote_channel);
    if((channel->stdout_buffer && buffer_get_rest_len(channel->stdout_buffer)>0)
            || (channel->stderr_buffer && buffer_get_rest_len(channel->stderr_buffer)>0))
        channel->delayed_close=1;
    else
        channel->open=0;
    if(!channel->remote_eof)
        ssh_log(session, SSH_LOG_PACKET,
            "Remote host not polite enough to send an eof before close");
    channel->remote_eof=1;
    /* the remote eof doesn't break things if there was still data into read
     * buffer because the eof is ignored until the buffer is empty. */
    leave_function();
}

static void channel_rcv_request(SSH_SESSION *session){
    STRING *request_s;
    char *request;
    u32 status;
    CHANNEL *channel=channel_from_msg(session);
    enter_function();
    if(!channel){
        ssh_log(session, SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session));
        leave_function();
        return;
    }
    request_s=buffer_get_ssh_string(session->in_buffer);
    if(!request_s){
        ssh_log(session, SSH_LOG_PACKET, "Invalid MSG_CHANNEL_REQUEST");
        leave_function();
        return;
    }
    buffer_get_u8(session->in_buffer,(u8 *)&status);
    request=string_to_char(request_s);
    if(!strcmp(request,"exit-status")){
        ssh_log(session, SSH_LOG_PACKET, "received exit-status");
        buffer_get_u32(session->in_buffer,&status);
        status=ntohl(status);
        channel->exit_status=status;
/* TODO do something with status, we might need it */
        free(request_s);
        free(request);
        leave_function();
        return ;
    }
    if(!strcmp(request,"exit-signal")){
        STRING *signal_s;
        char *signal;
        const char *core = "(core dumped)";
        u8 i;
        signal_s=buffer_get_ssh_string(session->in_buffer);
        if(!signal_s){
            ssh_log(session, SSH_LOG_PACKET, "Invalid MSG_CHANNEL_REQUEST");
            free(request_s);
            free(request);
            leave_function();
            return;
        }
        signal=string_to_char(signal_s);
        buffer_get_u8(session->in_buffer,&i);
        if(!i)
            core="";
        ssh_log(session, SSH_LOG_PACKET,
            "Remote connection closed by signal SIG %s %s", signal, core);
        free(signal_s);
        free(signal);
        free(request_s);
        free(request);
        leave_function();
        return;
    }
    ssh_log(session, SSH_LOG_PACKET, "Unknown request %s", request);
    free(request_s);
    free(request);
    leave_function();
}

/* channel_handle is called by wait_packet, ie, when there is channel informations to handle . */
void channel_handle(SSH_SESSION *session, int type){
    enter_function();
    ssh_log(session, SSH_LOG_PROTOCOL, "Channel_handle(%d)", type);
    switch(type){
        case SSH2_MSG_CHANNEL_WINDOW_ADJUST:
            channel_rcv_change_window(session);
            break;
        case SSH2_MSG_CHANNEL_DATA:
            channel_rcv_data(session,0);
            break;
        case SSH2_MSG_CHANNEL_EXTENDED_DATA:
            channel_rcv_data(session,1);
            break;
        case SSH2_MSG_CHANNEL_EOF:
            channel_rcv_eof(session);
            break;
        case SSH2_MSG_CHANNEL_CLOSE:
            channel_rcv_close(session);
            break;
        case SSH2_MSG_CHANNEL_REQUEST:
            channel_rcv_request(session);
            break;
        default:
            ssh_log(session, SSH_LOG_FUNCTIONS,
                "Unexpected message %d", type);
        }
    leave_function();
}

/* when data has been received from the ssh server, it can be applied to the known
    user function, with help of the callback, or inserted here */
/* XXX is the window changed ? */
int channel_default_bufferize(CHANNEL *channel, void *data, int len,
    int is_stderr) {
  struct ssh_session *session = channel->session;

  ssh_log(session, SSH_LOG_RARE,
      "placing %d bytes into channel buffer (stderr=%d)", len, is_stderr);
  if (! is_stderr) {
    /* stdout */
    if(channel->stdout_buffer == NULL) {
      channel->stdout_buffer = buffer_new();
      if (channel->stdout_buffer == NULL) {
        return -1;
      }
    }

    if (buffer_add_data(channel->stdout_buffer, data, len) < 0) {
      buffer_free(channel->stdout_buffer);
      return -1;
    }
  } else {
    /* stderr */
    if (channel->stderr_buffer == NULL) {
      channel->stderr_buffer = buffer_new();
      if (channel->stderr_buffer == NULL) {
        return -1;
      }
    }

    if (buffer_add_data(channel->stderr_buffer, data, len) < 0) {
      buffer_free(channel->stderr_buffer);
      return -1;
    }
  }

  return 0;
}

/** \brief open a session channel (suited for a shell. Not tcp Forwarding)
 * \param channel an allocated channel (see channel_new())
 * \return SSH_OK on success\n
 * SSH_ERROR on error
 * \see channel_open_forward()
 * \see channel_request_env()
 * \see channel_request_shell()
 * \see channel_request_exec()
 * \warning API changed from 0.11
 */
int channel_open_session(CHANNEL *channel){
#ifdef HAVE_SSH1
    if(channel->session->version==2)
#endif
        return channel_open(channel,"session",64000,32000,NULL);
#ifdef HAVE_SSH1
    else
        return channel_open_session1(channel);
#endif
}

/** \brief open a TCP/IP forwarding channel.
 * \param channel an allocated channel (see channel_new())
 * \param remotehost remote host to be connected (host name or IP)
 * \param remoteport remote port
 * \param sourcehost source host (your local computer). It's facultative and for logging purpose
 * \param localport source port (your local computer). It's facultative and for logging purpose
 * \return SSH_ERROR on error\n
 * SSH_OK on success
 * \warning API changed from 0.11
 */
int channel_open_forward(CHANNEL *channel, const char *remotehost,
    int remoteport, const char *sourcehost, int localport) {
  SSH_SESSION *session = channel->session;
  BUFFER *payload = NULL;
  STRING *str = NULL;
  int rc = SSH_ERROR;

  enter_function();

  payload = buffer_new();
  if (payload == NULL) {
    goto error;
  }
  str = string_from_char(remotehost);
  if (str == NULL) {
    goto error;
  }

  if (buffer_add_ssh_string(payload,str) < 0) {
    goto error;
  }
  if (buffer_add_u32(payload,htonl(remoteport)) < 0) {
    goto error;
  }

  string_free(str);
  str = string_from_char(sourcehost);
  if (str == NULL) {
    goto error;
  }

  if (buffer_add_ssh_string(payload, str) < 0) {
    goto error;
  }
  if (buffer_add_u32(payload,htonl(localport)) < 0) {
    goto error;
  }

  rc = channel_open(channel, "direct-tcpip", 64000, 32000, payload);

error:
  buffer_free(payload);
  string_free(str);

  leave_function();
  return rc;
}

/** \brief close and free a channel
 * \param channel channel to free
 * \warning any data unread on channel will be lost
 */
void channel_free(CHANNEL *channel) {
  SSH_SESSION *session = channel->session;
  enter_function();

  if (channel == NULL) {
    leave_function();
    return;
  }

  if (session->alive && channel->open) {
    channel_close(channel);
  }

  /* handle the "my channel is first on session list" case */
  if (session->channels == channel) {
    session->channels = channel->next;
  }

  /* handle the "my channel is the only on session list" case */
  if (channel->next == channel) {
    session->channels = NULL;
  } else {
    channel->prev->next = channel->next;
    channel->next->prev = channel->prev;
  }

  buffer_free(channel->stdout_buffer);
  buffer_free(channel->stderr_buffer);

  /* debug trick to catch use after frees */
  memset(channel, 'X', sizeof(CHANNEL));
  SAFE_FREE(channel);
  leave_function();
}

/** it doesn't close the channel. You may still read from it but not write.
 * \brief send an end of file on the channel
 * \param channel channel
 * \return SSH_ERROR on error\n
 * SSH_SUCCESS on success
 * \see channel_close()
 * \see channel_free()
 */
int channel_send_eof(CHANNEL *channel){
  SSH_SESSION *session = channel->session;
  int rc = SSH_ERROR;

  enter_function();

  if (buffer_add_u8(session->out_buffer, SSH2_MSG_CHANNEL_EOF) < 0) {
    goto error;
  }
  if (buffer_add_u32(session->out_buffer,htonl(channel->remote_channel)) < 0) {
    goto error;
  }
  rc = packet_send(session);
  ssh_log(session, SSH_LOG_PACKET,
      "Sent a EOF on client channel (%d:%d)",
      channel->local_channel,
      channel->remote_channel);

  channel->local_eof = 1;

  leave_function();
  return rc;
error:
  buffer_free(session->out_buffer);

  leave_function();
  return rc;
}

/** It sends an end of file and then closes the channel. You won't be able
 * to recover any data the server was going to send or was in buffers.
 * \brief close a channel
 * \param channel channel
 * \return SSH_ERROR on error\n
 * SSH_SUCCESS on success
 * \see channel_free()
 * \see channel_eof()
 */
int channel_close(CHANNEL *channel){
  SSH_SESSION *session = channel->session;
  int rc = 0;

  enter_function();

  if (!channel->local_eof) {
    rc = channel_send_eof(channel);
  }

  if (rc) {
    leave_function();
    return rc;
  }

  if (buffer_add_u8(session->out_buffer, SSH2_MSG_CHANNEL_CLOSE) < 0) {
    goto error;
  }
  if (buffer_add_u32(session->out_buffer, htonl(channel->remote_channel)) < 0) {
    goto error;
  }

  rc = packet_send(session);
  ssh_log(session, SSH_LOG_PACKET,
      "Sent a close on client channel (%d:%d)",
      channel->local_channel,
      channel->remote_channel);
  if(rc == SSH_OK) {
    channel->open = 0;
  }

  leave_function();
  return rc;
error:
  buffer_free(session->out_buffer);

  leave_function();
  return rc;
}

/** \brief blocking write on channel
 * \param channel channel
 * \param data pointer to data to write
 * \param len length of data
 * \return number of bytes written on success\n
 * SSH_ERROR on error
 * \see channel_read()
 */
int channel_write(CHANNEL *channel, const void *data, u32 len) {
    SSH_SESSION *session=channel->session;
    int effectivelen;
    int origlen=len;
    enter_function();
    if(channel->local_eof){
        ssh_set_error(session,SSH_REQUEST_DENIED,"Can't write to channel %d:%d"
            " after EOF was sent",channel->local_channel,channel->remote_channel);
        leave_function();
        return -1;
    }
    if(!channel->open || channel->delayed_close){
        ssh_set_error(session,SSH_REQUEST_DENIED,"Remote channel is closed");
        leave_function();
        return -1;
    }
#ifdef HAVE_SSH1
    if(channel->version==1){
        int err = channel_write1(channel,data,len);
        leave_function();
        return err;
    }
#endif
    while(len >0){
        if(channel->remote_window<len){
            ssh_log(session, SSH_LOG_PROTOCOL,
                "Remote window is %d bytes. going to write %d bytes",
                channel->remote_window,len);
            ssh_log(session, SSH_LOG_PROTOCOL,
                "Waiting for a growing window message...");
            // wonder what happens when the channel window is zero
            while(channel->remote_window==0){
                // parse every incoming packet
                packet_wait(channel->session,0,0);
            }
            effectivelen=len>channel->remote_window?channel->remote_window:len;
        } else
            effectivelen=len;
        if (buffer_add_u8(session->out_buffer,SSH2_MSG_CHANNEL_DATA) < 0) {
          goto error;
        }
        if (buffer_add_u32(session->out_buffer,htonl(channel->remote_channel)) < 0) {
          goto error;
        }
        if (buffer_add_u32(session->out_buffer,htonl(effectivelen)) < 0) {
          goto error;
        }
        if (buffer_add_data(session->out_buffer,data,effectivelen) < 0) {
          goto error;
        }

        if (packet_send(session) != SSH_OK) {
          leave_function();
          return SSH_ERROR;
        }
        ssh_log(session, SSH_LOG_RARE,
            "channel_write wrote %d bytes", effectivelen);
        channel->remote_window-=effectivelen;
        len -= effectivelen;
        data+=effectivelen;
    }
    leave_function();
    return origlen;
error:
    buffer_free(session->out_buffer);

    leave_function();
    return SSH_ERROR;
}

/** \brief returns if the channel is open or not
 * \param channel channel
 * \return 0 if channel is closed, nonzero otherwise
 * \see channel_is_closed()
 */
int channel_is_open(CHANNEL *channel){
    return (channel->open!=0 && channel->session->alive);
}

/** \brief returns if the channel is closed or not
 * \param channel channel
 * \return 0 if channel is opened, nonzero otherwise
 * \see channel_is_open()
 */

int channel_is_closed(CHANNEL *channel){
    return (channel->open==0 || !channel->session->alive);
}

/** \brief returns if the remote has sent an EOF
 * \param channel channel
 * \return 0 if there is no EOF, nonzero otherwise
 */
int channel_is_eof(CHANNEL *channel){
    if((channel->stdout_buffer && buffer_get_rest_len(channel->stdout_buffer)
                >0) || (channel->stderr_buffer && buffer_get_rest_len(
                    channel->stderr_buffer)>0))
        return 0;
    return (channel->remote_eof!=0);
}

/** \brief put the channel into nonblocking mode
 * \param channel channel
 * \param blocking boolean for blocking or nonblocking
 * \bug This functionnality is still under development and
 * doesn't work correctly
 */
void channel_set_blocking(CHANNEL *channel, int blocking){
    channel->blocking=blocking;
}

static int channel_request(CHANNEL *channel, const char *request,
    BUFFER *buffer, int reply) {
  SSH_SESSION *session = channel->session;
  STRING *req = NULL;
  int rc = SSH_ERROR;

  enter_function();

  req = string_from_char(request);
  if (req == NULL) {
    goto error;
  }
  if (buffer_add_u8(session->out_buffer, SSH2_MSG_CHANNEL_REQUEST) < 0) {
    goto error;
  }
  if (buffer_add_u32(session->out_buffer, htonl(channel->remote_channel)) < 0) {
    goto error;
  }
  if (buffer_add_ssh_string(session->out_buffer, req) < 0) {
    goto error;
  }
  if (buffer_add_u8(session->out_buffer, reply ? 1 : 0) < 0) {
    goto error;
  }
  string_free(req);

  if (buffer) {
    if (buffer_add_data(session->out_buffer, buffer_get(buffer),
        buffer_get_len(buffer)) < 0) {
      goto error;
    }
  }

  if (packet_send(session) != SSH_OK) {
    leave_function();
    return rc;
  }

  ssh_log(session, SSH_LOG_RARE,
      "Sent a SSH_MSG_CHANNEL_REQUEST %s", request);
  if (! reply) {
    leave_function();
    return SSH_OK;
  }

  rc = packet_wait(session, SSH2_MSG_CHANNEL_SUCCESS, 1);
  if (rc) {
    if (session->in_packet.type == SSH2_MSG_CHANNEL_FAILURE) {
      ssh_log(session, SSH_LOG_PACKET,
          "%s channel request failed", request);
      ssh_set_error(session, SSH_REQUEST_DENIED,
          "Channel request %s failed", request);
    } else {
      ssh_log(session, SSH_LOG_RARE,
          "Received an unexpected %d message", session->in_packet.type);
    }
  } else {
    ssh_log(session, SSH_LOG_RARE, "Received a SUCCESS");
  }

  leave_function();
  return rc;
error:
  buffer_free(session->out_buffer);
  string_free(req);

  leave_function();
  return rc;
}

/** \brief requests a pty with a specific type and size
 * \param channel channel
 * \param terminal terminal type ("vt100, xterm,...")
 * \param col number of cols
 * \param row number of rows
 * \return SSH_SUCCESS on success\n
 * SSH_ERROR on error
 */
int channel_request_pty_size(CHANNEL *channel, const char *terminal,
    int col, int row) {
  SSH_SESSION *session = channel->session;
  STRING *term = NULL;
  BUFFER *buffer = NULL;
  int rc = SSH_ERROR;

  enter_function();
#ifdef HAVE_SSH1
  if (channel->version==1) {
    err = channel_request_pty_size1(channel,terminal, col, row);
    leave_function();
    return rc;
    }
#endif
  buffer = buffer_new();
  if (buffer == NULL) {
    goto error;
  }

  term = string_from_char(terminal);
  if (term == NULL) {
    goto error;
  }

  if (buffer_add_ssh_string(buffer, term) < 0) {
    goto error;
  }
  if (buffer_add_u32(buffer, htonl(col)) < 0) {
    goto error;
  }
  if (buffer_add_u32(buffer, htonl(row)) < 0) {
    goto error;
  }
  if (buffer_add_u32(buffer, 0) < 0) {
    goto error;
  }
  if (buffer_add_u32(buffer, 0) < 0) {
    goto error;
  }
  /* a 0byte string */
  if (buffer_add_u32(buffer, htonl(1)) < 0) {
    goto error;
  }
  if (buffer_add_u8(buffer, 0) < 0) {
    goto error;
  }

  rc = channel_request(channel, "pty-req", buffer, 1);
error:
  buffer_free(buffer);
  string_free(term);
  leave_function();
  return rc;
}

/** \brief requests a pty
 * \param channel channel
 * \see channel_request_pty_size()
 * \return SSH_SUCCESS on success\n
 * SSH_ERROR on error
 */
int channel_request_pty(CHANNEL *channel){
    return channel_request_pty_size(channel,"xterm",80,24);
}

/** \brief change the size of the terminal associated to a channel
 * \param channel channel
 * \param cols new number of cols
 * \param rows new number of rows
 * \warning Do not call it from a signal handler if you are not
 * sure any other libssh function using the same channel/session
 * is running at same time (not 100% threadsafe)
 */
int channel_change_pty_size(CHANNEL *channel, int cols, int rows) {
  SSH_SESSION *session = channel->session;
  BUFFER *buffer = NULL;
  int rc = SSH_ERROR;

  enter_function();

#ifdef HAVE_SSH1
  if (channel->version == 1) {
    rc = channel_change_pty_size1(channel,cols,rows);
    leave_function();
    return rc;
  }
#endif

  buffer = buffer_new();
  if (buffer == NULL) {
    goto error;
  }

  if (buffer_add_u32(buffer, htonl(cols)) < 0) {
    goto error;
  }
  if (buffer_add_u32(buffer, htonl(rows)) < 0) {
    goto error;
  }
  if (buffer_add_u32(buffer, 0) < 0) {
    goto error;
  }
  if (buffer_add_u32(buffer, 0) < 0) {
    goto error;
  }

  rc = channel_request(channel, "window-change", buffer, 0);
error:
  buffer_free(buffer);

  leave_function();
  return rc;
}

/** \brief requests a shell
 * \param channel
 * \returns SSH_SUCCESS on success\n
 * SSH_ERROR on error
 */
int channel_request_shell(CHANNEL *channel){
#ifdef HAVE_SSH1
    if(channel->version==1)
        return channel_request_shell1(channel);
#endif
    return channel_request(channel,"shell",NULL,1);
}

/** \brief requests a subsystem (for example sftp)
 * \param channel channel
 * \param system subsystem to request (for example sftp)
 * \return SSH_SUCCESS on success\n
 * SSH_ERROR on error
 * \warning you normally don't have to call it to have sftp
 * \see sftp_new()
 */
int channel_request_subsystem(CHANNEL *channel, const char *sys) {
  BUFFER *buffer = NULL;
  STRING *subsystem = NULL;
  int rc = SSH_ERROR;

  buffer = buffer_new();
  if (buffer == NULL) {
    goto error;
  }

  subsystem = string_from_char(sys);
  if (subsystem == NULL) {
    goto error;
  }

  if (buffer_add_ssh_string(buffer, subsystem) < 0) {
    goto error;
  }

  rc = channel_request(channel, "subsystem", buffer, 1);
error:
  buffer_free(buffer);
  string_free(subsystem);

  return rc;
}

int channel_request_sftp( CHANNEL *channel){
    return channel_request_subsystem(channel, "sftp");
}

/** \brief set the environement variables
 * \param channel channel
 * \param name name of the variable
 * \param value value
 * \return SSH_SUCCESS on success\n
 * SSH_ERROR on error
 * \warning some environement variables may be refused by security
 * */
int channel_request_env(CHANNEL *channel, const char *name, const char *value) {
  BUFFER *buffer = NULL;
  STRING *str = NULL;
  int rc;

  buffer = buffer_new();
  if (buffer == NULL) {
    goto error;
  }

  str = string_from_char(name);
  if (str == NULL) {
    goto error;
  }

  if (buffer_add_ssh_string(buffer, str) < 0) {
    goto error;
  }

  string_free(string);
  str = string_from_char(value);
  if (str == NULL) {
    goto error;
  }

  if (buffer_add_ssh_string(buffer, str) < 0) {
    goto error;
  }

  rc = channel_request(channel,"env",buffer,1);
error:
  buffer_free(buffer);
  string_free(str);

  return rc;
}

/** it's similar to sh -c "command"
 * \brief run a shell command without an interactive shell
 * \param channel channel
 * \param cmd command to execute (by ex. "ls ~/ -al | grep -i reports")
 * \return SSH_SUCCESS on success\n
 * SSH_ERROR on error
 * \see channel_request_shell()
 */
int channel_request_exec(CHANNEL *channel, const char *cmd) {
  BUFFER *buffer = NULL;
  STRING *command = NULL;
  int rc = SSH_ERROR;

#ifdef HAVE_SSH1
  if (channel->version == 1) {
    return channel_request_exec1(channel, cmd);
  }
#endif

  buffer = buffer_new();
  if (buffer == NULL) {
    goto error;
  }

  command = string_from_char(cmd);
  if (command == NULL) {
    goto error;
  }

  if (buffer_add_ssh_string(buffer, command) < 0) {
    goto error;
  }

  rc = channel_request(channel, "exec", buffer, 1);
error:
  buffer_free(buffer);
  string_free(command);
  return rc;
}

/* TODO : fix the delayed close thing */
/* TODO : fix the blocking behaviours */
/* reads into a channel and put result into buffer */
/* returns number of bytes read, 0 if eof or such and -1 in case of error */
/* if bytes != 0, the exact number of bytes are going to be read */
/** \brief reads data from a channel
 * \param channel channel
 * \param buffer buffer which will get the data
 * \param bytes number of bytes to be read. If it is bigger
 * than 0, the exact size will be read, else (bytes=0) it will return
 * once anything is available
 * \param is_stderr boolean value to mark reading from the stderr flow.
 * \return number of bytes read\n
 * 0 on end of file\n
 * SSH_ERROR on error
 */
int channel_read(CHANNEL *channel, BUFFER *buffer, u32 bytes, int is_stderr) {
    BUFFER *stdbuf=NULL;
    SSH_SESSION *session=channel->session;
    u32 maxread=bytes;
    u32 len;

    buffer_reinit(buffer);
    enter_function();
    if(bytes==0)
        maxread=MAX_PACKET_LEN;
    /* maybe i should always set a buffer to avoid races between channel_default_bufferize and channel_read */
    if(is_stderr)
        stdbuf=channel->stderr_buffer;
    else
        stdbuf=channel->stdout_buffer;

    /* We may have problem if the window is too small to accept as much data as asked */
    ssh_log(session, SSH_LOG_PROTOCOL,
        "Read (%d) buffered : %d bytes. Window: %d",
        bytes,
        buffer_get_rest_len(stdbuf),
        channel->local_window);
    if(bytes > buffer_get_rest_len(stdbuf) + channel->local_window) {
      if (grow_window(session, channel, bytes - buffer_get_rest_len(stdbuf)) < 0) {
        leave_function();
        return -1;
      }
    }
    /* block reading if asked bytes=0 */
    while((buffer_get_rest_len(stdbuf)==0) || (buffer_get_rest_len(stdbuf) < bytes)){
        if(channel->remote_eof && buffer_get_rest_len(stdbuf)==0){
            leave_function();
        	return 0;
        }
        if(channel->remote_eof)
            break; /* return the resting bytes in buffer */
        if(buffer_get_rest_len(stdbuf)>=maxread) // stop reading when buffer is full enough
            break;
        if(packet_read(session)||packet_translate(session)){
            leave_function();
        	return -1;
        }
        packet_parse(session);
    }
    if(channel->local_window < WINDOWLIMIT) {
      if (grow_window(session, channel, 0) < 0) {
        leave_function();
        return -1;
      }
    }
    if(bytes==0){
        /* write the ful buffer informations */
        if (buffer_add_data(buffer, buffer_get_rest(stdbuf),
              buffer_get_rest_len(stdbuf)) < 0) {
          leave_function();
          return -1;
        }
        buffer_reinit(stdbuf);
    } else {
        len=buffer_get_rest_len(stdbuf);
        len= (len>bytes?bytes:len); /* read bytes bytes if len is greater, everything otherwise */
        if (buffer_add_data(buffer, buffer_get_rest(stdbuf), len) < 0) {
          leave_function();
          return -1;
        }
        buffer_pass_bytes(stdbuf,len);
    }
    leave_function();
    return buffer_get_len(buffer);
}

/** \brief polls the channel for data to read
 * \param channel channel
 * \param is_stderr boolean to select the stderr stream
 * \return number of bytes available for reading\n
 * 0 if nothing is available\n
 * SSH_ERROR on error
 * \warning When the channel is in EOF state, the function returns 1
 * \see channel_is_eof()
 */
int channel_poll(CHANNEL *channel, int is_stderr){
{'fraction_non_alphanumeric': 0.11286565983564534, 'fraction_numerical': 0.009374954223856329, 'mean_word_length': 3.0618789789968464, 'pattern_counts': {'":': 0, '<': 74, '<?xml version=': 0, '>': 457, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5477756', 'n_tokens_mistral': 22129, 'n_tokens_neox': 21232, 'n_words': 5368}