Mercurial > dropbear
view common-channel.c @ 1523:1d163552145f coverity
merge coverity
author | Matt Johnston <matt@ucc.asn.au> |
---|---|
date | Mon, 19 Feb 2018 23:14:49 +0800 |
parents | 06d52bcb8094 |
children | 5916af64acd4 |
line wrap: on
line source
/* * Dropbear SSH * * Copyright (c) 2002-2004 Matt Johnston * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ /* Handle the multiplexed channels, such as sessions, x11, agent connections */ #include "includes.h" #include "session.h" #include "packet.h" #include "ssh.h" #include "buffer.h" #include "circbuffer.h" #include "dbutil.h" #include "channel.h" #include "listener.h" #include "runopts.h" #include "netio.h" static void send_msg_channel_open_failure(unsigned int remotechan, int reason, const char *text, const char *lang); static void send_msg_channel_open_confirmation(const struct Channel* channel, unsigned int recvwindow, unsigned int recvmaxpacket); static int writechannel(struct Channel* channel, int fd, circbuffer *cbuf, const unsigned char *moredata, unsigned int *morelen); static void send_msg_channel_window_adjust(const struct Channel *channel, unsigned int incr); static void send_msg_channel_data(struct Channel *channel, int isextended); static void send_msg_channel_eof(struct Channel *channel); static void send_msg_channel_close(struct Channel *channel); static void remove_channel(struct Channel *channel); static unsigned int write_pending(const struct Channel * channel); static void check_close(struct Channel *channel); static void close_chan_fd(struct Channel *channel, int fd, int how); #define FD_UNINIT (-2) #define FD_CLOSED (-1) #define ERRFD_IS_READ(channel) ((channel)->extrabuf == NULL) #define ERRFD_IS_WRITE(channel) (!ERRFD_IS_READ(channel)) /* allow space for: * 1 byte byte SSH_MSG_CHANNEL_DATA * 4 bytes uint32 recipient channel * 4 bytes string data */ #define RECV_MAX_CHANNEL_DATA_LEN (RECV_MAX_PAYLOAD_LEN-(1+4+4)) /* Initialise all the channels */ void chaninitialise(const struct ChanType *chantypes[]) { /* may as well create space for a single channel */ ses.channels = (struct Channel**)m_malloc(sizeof(struct Channel*)); ses.chansize = 1; ses.channels[0] = NULL; ses.chancount = 0; ses.chantypes = chantypes; #if DROPBEAR_LISTENERS listeners_initialise(); #endif } /* Clean up channels, freeing allocated memory */ void chancleanup() { unsigned int i; TRACE(("enter chancleanup")) for (i = 0; i < ses.chansize; i++) { if (ses.channels[i] != NULL) { TRACE(("channel %d closing", i)) remove_channel(ses.channels[i]); } } m_free(ses.channels); TRACE(("leave chancleanup")) } /* Create a new channel entry, send a reply confirm or failure */ /* If remotechan, transwindow and transmaxpacket are not know (for a new * outgoing connection, with them to be filled on confirmation), they should * all be set to 0 */ static struct Channel* newchannel(unsigned int remotechan, const struct ChanType *type, unsigned int transwindow, unsigned int transmaxpacket) { struct Channel * newchan; unsigned int i, j; TRACE(("enter newchannel")) /* first see if we can use existing channels */ for (i = 0; i < ses.chansize; i++) { if (ses.channels[i] == NULL) { break; } } /* otherwise extend the list */ if (i == ses.chansize) { if (ses.chansize >= MAX_CHANNELS) { TRACE(("leave newchannel: max chans reached")) return NULL; } /* extend the channels */ ses.channels = (struct Channel**)m_realloc(ses.channels, (ses.chansize+CHAN_EXTEND_SIZE)*sizeof(struct Channel*)); ses.chansize += CHAN_EXTEND_SIZE; /* set the new channels to null */ for (j = i; j < ses.chansize; j++) { ses.channels[j] = NULL; } } newchan = (struct Channel*)m_malloc(sizeof(struct Channel)); newchan->type = type; newchan->index = i; newchan->sent_close = newchan->recv_close = 0; newchan->sent_eof = newchan->recv_eof = 0; newchan->close_handler_done = 0; newchan->remotechan = remotechan; newchan->transwindow = transwindow; newchan->transmaxpacket = transmaxpacket; newchan->typedata = NULL; newchan->writefd = FD_UNINIT; newchan->readfd = FD_UNINIT; newchan->errfd = FD_CLOSED; /* this isn't always set to start with */ newchan->await_open = 0; newchan->flushing = 0; newchan->writebuf = cbuf_new(opts.recv_window); newchan->recvwindow = opts.recv_window; newchan->extrabuf = NULL; /* The user code can set it up */ newchan->recvdonelen = 0; newchan->recvmaxpacket = RECV_MAX_CHANNEL_DATA_LEN; newchan->prio = DROPBEAR_CHANNEL_PRIO_EARLY; /* inithandler sets it */ ses.channels[i] = newchan; ses.chancount++; TRACE(("leave newchannel")) return newchan; } /* Returns the channel structure corresponding to the channel in the current * data packet (ses.payload must be positioned appropriately). * A valid channel is always returns, it will fail fatally with an unknown * channel */ static struct Channel* getchannel_msg(const char* kind) { unsigned int chan; chan = buf_getint(ses.payload); if (chan >= ses.chansize || ses.channels[chan] == NULL) { if (kind) { dropbear_exit("%s for unknown channel %d", kind, chan); } else { dropbear_exit("Unknown channel %d", chan); } } return ses.channels[chan]; } struct Channel* getchannel() { return getchannel_msg(NULL); } /* Iterate through the channels, performing IO if available */ void channelio(const fd_set *readfds, const fd_set *writefds) { /* Listeners such as TCP, X11, agent-auth */ struct Channel *channel; unsigned int i; /* foreach channel */ for (i = 0; i < ses.chansize; i++) { /* Close checking only needs to occur for channels that had IO events */ int do_check_close = 0; channel = ses.channels[i]; if (channel == NULL) { /* only process in-use channels */ continue; } /* read data and send it over the wire */ if (channel->readfd >= 0 && FD_ISSET(channel->readfd, readfds)) { TRACE(("send normal readfd")) send_msg_channel_data(channel, 0); do_check_close = 1; } /* read stderr data and send it over the wire */ if (ERRFD_IS_READ(channel) && channel->errfd >= 0 && FD_ISSET(channel->errfd, readfds)) { TRACE(("send normal errfd")) send_msg_channel_data(channel, 1); do_check_close = 1; } /* write to program/pipe stdin */ if (channel->writefd >= 0 && FD_ISSET(channel->writefd, writefds)) { writechannel(channel, channel->writefd, channel->writebuf, NULL, NULL); do_check_close = 1; } /* stderr for client mode */ if (ERRFD_IS_WRITE(channel) && channel->errfd >= 0 && FD_ISSET(channel->errfd, writefds)) { writechannel(channel, channel->errfd, channel->extrabuf, NULL, NULL); do_check_close = 1; } if (ses.channel_signal_pending) { /* SIGCHLD can change channel state for server sessions */ do_check_close = 1; } /* handle any channel closing etc */ if (do_check_close) { check_close(channel); } } #if DROPBEAR_LISTENERS handle_listeners(readfds); #endif } /* Returns true if there is data remaining to be written to stdin or * stderr of a channel's endpoint. */ static unsigned int write_pending(const struct Channel * channel) { if (channel->writefd >= 0 && cbuf_getused(channel->writebuf) > 0) { return 1; } else if (channel->errfd >= 0 && channel->extrabuf && cbuf_getused(channel->extrabuf) > 0) { return 1; } return 0; } /* EOF/close handling */ static void check_close(struct Channel *channel) { int close_allowed = 0; TRACE2(("check_close: writefd %d, readfd %d, errfd %d, sent_close %d, recv_close %d", channel->writefd, channel->readfd, channel->errfd, channel->sent_close, channel->recv_close)) TRACE2(("writebuf size %d extrabuf size %d", channel->writebuf ? cbuf_getused(channel->writebuf) : 0, channel->extrabuf ? cbuf_getused(channel->extrabuf) : 0)) if (!channel->flushing && !channel->close_handler_done && channel->type->check_close && channel->type->check_close(channel)) { channel->flushing = 1; } /* if a type-specific check_close is defined we will only exit once that has been triggered. this is only used for a server "session" channel, to ensure that the shell has exited (and the exit status retrieved) before we close things up. */ if (!channel->type->check_close || channel->close_handler_done || channel->type->check_close(channel)) { close_allowed = 1; } if (channel->recv_close && !write_pending(channel) && close_allowed) { if (!channel->sent_close) { TRACE(("Sending MSG_CHANNEL_CLOSE in response to same.")) send_msg_channel_close(channel); } remove_channel(channel); return; } if ((channel->recv_eof && !write_pending(channel)) /* have a server "session" and child has exited */ || (channel->type->check_close && close_allowed)) { close_chan_fd(channel, channel->writefd, SHUT_WR); } /* Special handling for flushing read data after an exit. We read regardless of whether the select FD was set, and if there isn't data available, the channel will get closed. */ if (channel->flushing) { TRACE(("might send data, flushing")) if (channel->readfd >= 0 && channel->transwindow > 0) { TRACE(("send data readfd")) send_msg_channel_data(channel, 0); } if (ERRFD_IS_READ(channel) && channel->errfd >= 0 && channel->transwindow > 0) { TRACE(("send data errfd")) send_msg_channel_data(channel, 1); } } /* If we're not going to send any more data, send EOF */ if (!channel->sent_eof && channel->readfd == FD_CLOSED && (ERRFD_IS_WRITE(channel) || channel->errfd == FD_CLOSED)) { send_msg_channel_eof(channel); } /* And if we can't receive any more data from them either, close up */ if (channel->readfd == FD_CLOSED && channel->writefd == FD_CLOSED && (ERRFD_IS_WRITE(channel) || channel->errfd == FD_CLOSED) && !channel->sent_close && close_allowed && !write_pending(channel)) { TRACE(("sending close, readfd is closed")) send_msg_channel_close(channel); } } /* Check whether a deferred (EINPROGRESS) connect() was successful, and * if so, set up the channel properly. Otherwise, the channel is cleaned up, so * it is important that the channel reference isn't used after a call to this * function */ void channel_connect_done(int result, int sock, void* user_data, const char* UNUSED(errstring)) {