diff --git a/crypto/openssh/README.hpn b/crypto/openssh/README.hpn new file mode 100644 index 000000000000..674827fb6b4d --- /dev/null +++ b/crypto/openssh/README.hpn @@ -0,0 +1,120 @@ +Notes: + +NONE CIPHER: + To use the NONE option you must have the NoneEnabled switch set on the server + and you MUST have *both* NoneEnabled and NoneSwitch set to yes on the client. + The NONE feature works with ALL ssh subsystems (as far as we can tell) + as long as there is no tty allocated. + If a user uses the -T switch to prevent a tty being created the NONE cipher + will be disabled. + + +PERFORMANCE: + The performance increase will only be as good as the network and TCP stack + tuning on the reciever side of the connection allows. As a rule of thumb a + user will need at least 10Mb/s connection with a 100ms RTT to see a doubling + of performance. + The HPN-SSH home page http://www.psc.edu/networking/projects/hpn-ssh + describes this in greater detail. + + +BUFFER SIZES: +- if HPN is disabled the receive buffer size will be set to the OpenSSH default + of 64K. + +- if a HPN system connects to a non-HPN system the receive buffer will + be set to the HPNBufferSize value. The default is 2MB but user adjustable. + +- If a HPN to HPN connection is established a number of different things might + happen based on the user options and conditions. + + Conditions: HPNBufferSize NOT Set, TCPRcvBufPoll enabled, TCPRcvBuf NOT Set + Result: HPN Buffer Size = up to 64MB + This is the default state. The HPN buffer size will grow to a maximum of + 64MB as the TCP receive buffer grows. The maximum HPN Buffer size of 64MB + is geared towards 10GigE transcontinental connections. + + Conditions: HPNBufferSize NOT Set, TCPRcvBufPoll disabled, TCPRcvBuf NOT Set + Result: HPN Buffer Size = TCP receive buffer value. + Users on non-autotuning systesm should disable TCPRcvBufPoll in the + ssh_cofig and sshd_config + + Conditions: HPNBufferSize SET, TCPRcvBufPoll disabled, TCPRcvBuf NOT Set + Result: HPN Buffer Size = minmum of TCP receive buffer and HPNBufferSize. + This would be the system defined TCP receive buffer (RWIN). + + Conditions: HPNBufferSize SET, TCPRcvBufPoll disabled, TCPRcvBuf SET + Result: HPN Buffer Size = minmum of TCPRcvBuf and HPNBufferSize. + Generally there is no need to set both. + + Conditions: HPNBufferSize SET, TCPRcvBufPoll enabled, TCPRcvBuf NOT Set + Result: HPN Buffer Size = grows to HPNBufferSize + The buffer will grow up to the maximum size specified here. + + Conditions: HPNBufferSize SET, TCPRcvBufPoll enabled, TCPRcvBuf SET + Result: HPN Buffer Size = minmum of TCPRcvBuf and HPNBufferSize. + Generally there is no need to set both of these, especially on autotuning + systems. However, if the users wishes to override the autotuning this would + be one way to do it. + + Conditions: HPNBufferSize NOT Set, TCPRcvBufPoll enabled, TCPRcvBuf SET + Result: HPN Buffer Size = TCPRcvBuf. + This will override autotuning and set the TCP recieve buffer to the user + defined value. + + +HPN SPECIFIC CONFIGURATION OPTIONS: + +- HPNDisabled=[yes/no] client/server + In some situations, such as transfers on a local area network, the impact + of the HPN code produces a net decrease in performance. In these cases it is + helpful to disable the HPN functionality. By default HPNDisabled is set to no. + +- HPNBufferSize=[int]KB client/server + This is the default buffer size the HPN functionality uses when interacting + with non-HPN SSH installations. Conceptually this is similar to the TcpRcvBuf + option as applied to the internal SSH flow control. This value can range from + 1KB to 64MB (1-65536). Use of oversized or undersized buffers can cause + performance problems depending on the roud trip time of the network path. + The default size of this buffer is 2MB. + +- TcpRcvBufPoll=[yes/no] client/server + Enable or disable the polling of the TCP receive buffer through the life + of the connection. You would want to make sure that this option is enabled + for systems making use of autotuning kernels (linux 2.4.24+, 2.6, MS Vista, + FreeBSD 7.x and later). Default is yes. + +- TcpRcvBuf=[int]KB client + Set the TCP socket receive buffer to n Kilobytes. It can be set up to the + maximum socket size allowed by the system. This is useful in situations where + the TCP receive window is set low but the maximum buffer size is set higher + (as is typical). This works on a per TCP connection basis. You can also use + this to artifically limit the transfer rate of the connection. In these cases + the throughput will be no more than n/RTT. The minimum buffer size is 1KB. + Default is the current system wide TCP receive buffer size. + +- NoneEnabled=[yes/no] client/server + Enable or disable the use of the None cipher. Care must always be used when + enabling this as it will allow users to send data in the clear. However, it + is important to note that authentication information remains encrypted even + if this option is enabled. Set to no by default. + +- NoneSwitch=[yes/no] client + Switch the encryption cipher being used to the None cipher after + authentication takes place. NoneEnabled must be enabled on both the client + and server side of the connection. When the connection switches to the NONE + cipher a warning is sent to STDERR. The connection attempt will fail with an + error if a client requests a NoneSwitch from the server that does not + explicitly have NoneEnabled set to yes. + Note: The NONE cipher cannot be used in interactive (shell) sessions and it + will fail silently. Set to no by default. + + +CREDITS: + + This patch was conceived, designed, and led by Chris Rapier (rapier@psc.edu) + The majority of the actual coding for versions up to HPN12v1 was performed + by Michael Stevens (mstevens@andrew.cmu.edu). + The MT-AES-CTR cipher was implemented by Ben Bennet (ben@psc.edu). + This work was financed, in part, by Cisco System, Inc., the National Library + of Medicine, and the National Science Foundation. diff --git a/crypto/openssh/buffer.c b/crypto/openssh/buffer.c index ae9700344d2a..a3202c601d57 100644 --- a/crypto/openssh/buffer.c +++ b/crypto/openssh/buffer.c @@ -1,252 +1,260 @@ /* $OpenBSD: buffer.c,v 1.32 2010/02/09 03:56:28 djm Exp $ */ +/* $FreeBSD$ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved * Functions for manipulating fifo buffers (that can grow if needed). * * As far as I am concerned, the code I have written for this software * can be used freely for any purpose. Any derived versions of this * software must be clearly marked as such, and if the derived work is * incompatible with the protocol description in the RFC file, it must be * called by a name other than "ssh" or "Secure Shell". */ #include "includes.h" #include #include #include #include #include "xmalloc.h" #include "buffer.h" #include "log.h" #define BUFFER_MAX_CHUNK 0x100000 -#define BUFFER_MAX_LEN 0xa00000 +#define BUFFER_MAX_LEN 0x4000000 /* 64MB */ #define BUFFER_ALLOCSZ 0x008000 /* Initializes the buffer structure. */ void buffer_init(Buffer *buffer) { const u_int len = 4096; buffer->alloc = 0; buffer->buf = xmalloc(len); buffer->alloc = len; buffer->offset = 0; buffer->end = 0; } /* Frees any memory used for the buffer. */ void buffer_free(Buffer *buffer) { if (buffer->alloc > 0) { memset(buffer->buf, 0, buffer->alloc); buffer->alloc = 0; xfree(buffer->buf); } } /* * Clears any data from the buffer, making it empty. This does not actually * zero the memory. */ void buffer_clear(Buffer *buffer) { buffer->offset = 0; buffer->end = 0; } /* Appends data to the buffer, expanding it if necessary. */ void buffer_append(Buffer *buffer, const void *data, u_int len) { void *p; p = buffer_append_space(buffer, len); memcpy(p, data, len); } static int buffer_compact(Buffer *buffer) { /* * If the buffer is quite empty, but all data is at the end, move the * data to the beginning. */ if (buffer->offset > MIN(buffer->alloc, BUFFER_MAX_CHUNK)) { memmove(buffer->buf, buffer->buf + buffer->offset, buffer->end - buffer->offset); buffer->end -= buffer->offset; buffer->offset = 0; return (1); } return (0); } /* * Appends space to the buffer, expanding the buffer if necessary. This does * not actually copy the data into the buffer, but instead returns a pointer * to the allocated region. */ void * buffer_append_space(Buffer *buffer, u_int len) { u_int newlen; void *p; if (len > BUFFER_MAX_CHUNK) fatal("buffer_append_space: len %u not supported", len); /* If the buffer is empty, start using it from the beginning. */ if (buffer->offset == buffer->end) { buffer->offset = 0; buffer->end = 0; } restart: /* If there is enough space to store all data, store it now. */ if (buffer->end + len < buffer->alloc) { p = buffer->buf + buffer->end; buffer->end += len; return p; } /* Compact data back to the start of the buffer if necessary */ if (buffer_compact(buffer)) goto restart; /* Increase the size of the buffer and retry. */ newlen = roundup(buffer->alloc + len, BUFFER_ALLOCSZ); if (newlen > BUFFER_MAX_LEN) fatal("buffer_append_space: alloc %u not supported", newlen); buffer->buf = xrealloc(buffer->buf, 1, newlen); buffer->alloc = newlen; goto restart; /* NOTREACHED */ } /* * Check whether an allocation of 'len' will fit in the buffer * This must follow the same math as buffer_append_space */ int buffer_check_alloc(Buffer *buffer, u_int len) { if (buffer->offset == buffer->end) { buffer->offset = 0; buffer->end = 0; } restart: if (buffer->end + len < buffer->alloc) return (1); if (buffer_compact(buffer)) goto restart; if (roundup(buffer->alloc + len, BUFFER_ALLOCSZ) <= BUFFER_MAX_LEN) return (1); return (0); } /* Returns the number of bytes of data in the buffer. */ u_int buffer_len(const Buffer *buffer) { return buffer->end - buffer->offset; } +/* Returns the maximum number of bytes of data that may be in the buffer. */ +u_int +buffer_get_max_len(void) +{ + return (BUFFER_MAX_LEN); +} + /* Gets data from the beginning of the buffer. */ int buffer_get_ret(Buffer *buffer, void *buf, u_int len) { if (len > buffer->end - buffer->offset) { error("buffer_get_ret: trying to get more bytes %d than in buffer %d", len, buffer->end - buffer->offset); return (-1); } memcpy(buf, buffer->buf + buffer->offset, len); buffer->offset += len; return (0); } void buffer_get(Buffer *buffer, void *buf, u_int len) { if (buffer_get_ret(buffer, buf, len) == -1) fatal("buffer_get: buffer error"); } /* Consumes the given number of bytes from the beginning of the buffer. */ int buffer_consume_ret(Buffer *buffer, u_int bytes) { if (bytes > buffer->end - buffer->offset) { error("buffer_consume_ret: trying to get more bytes than in buffer"); return (-1); } buffer->offset += bytes; return (0); } void buffer_consume(Buffer *buffer, u_int bytes) { if (buffer_consume_ret(buffer, bytes) == -1) fatal("buffer_consume: buffer error"); } /* Consumes the given number of bytes from the end of the buffer. */ int buffer_consume_end_ret(Buffer *buffer, u_int bytes) { if (bytes > buffer->end - buffer->offset) return (-1); buffer->end -= bytes; return (0); } void buffer_consume_end(Buffer *buffer, u_int bytes) { if (buffer_consume_end_ret(buffer, bytes) == -1) fatal("buffer_consume_end: trying to get more bytes than in buffer"); } /* Returns a pointer to the first used byte in the buffer. */ void * buffer_ptr(const Buffer *buffer) { return buffer->buf + buffer->offset; } /* Dumps the contents of the buffer to stderr. */ void buffer_dump(const Buffer *buffer) { u_int i; u_char *ucp = buffer->buf; for (i = buffer->offset; i < buffer->end; i++) { fprintf(stderr, "%02x", ucp[i]); if ((i-buffer->offset)%16==15) fprintf(stderr, "\r\n"); else if ((i-buffer->offset)%2==1) fprintf(stderr, " "); } fprintf(stderr, "\r\n"); } diff --git a/crypto/openssh/buffer.h b/crypto/openssh/buffer.h index e2a9dd1002a0..c35f53fe428a 100644 --- a/crypto/openssh/buffer.h +++ b/crypto/openssh/buffer.h @@ -1,98 +1,101 @@ /* $OpenBSD: buffer.h,v 1.21 2010/08/31 11:54:45 djm Exp $ */ +/* $FreeBSD$ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved * Code for manipulating FIFO buffers. * * As far as I am concerned, the code I have written for this software * can be used freely for any purpose. Any derived versions of this * software must be clearly marked as such, and if the derived work is * incompatible with the protocol description in the RFC file, it must be * called by a name other than "ssh" or "Secure Shell". */ #ifndef BUFFER_H #define BUFFER_H typedef struct { u_char *buf; /* Buffer for data. */ u_int alloc; /* Number of bytes allocated for data. */ u_int offset; /* Offset of first byte containing data. */ u_int end; /* Offset of last byte containing data. */ } Buffer; void buffer_init(Buffer *); void buffer_clear(Buffer *); void buffer_free(Buffer *); u_int buffer_len(const Buffer *); void *buffer_ptr(const Buffer *); void buffer_append(Buffer *, const void *, u_int); void *buffer_append_space(Buffer *, u_int); int buffer_check_alloc(Buffer *, u_int); void buffer_get(Buffer *, void *, u_int); void buffer_consume(Buffer *, u_int); void buffer_consume_end(Buffer *, u_int); void buffer_dump(const Buffer *); int buffer_get_ret(Buffer *, void *, u_int); int buffer_consume_ret(Buffer *, u_int); int buffer_consume_end_ret(Buffer *, u_int); +u_int buffer_get_max_len(void); + #include void buffer_put_bignum(Buffer *, const BIGNUM *); void buffer_put_bignum2(Buffer *, const BIGNUM *); void buffer_get_bignum(Buffer *, BIGNUM *); void buffer_get_bignum2(Buffer *, BIGNUM *); u_short buffer_get_short(Buffer *); void buffer_put_short(Buffer *, u_short); u_int buffer_get_int(Buffer *); void buffer_put_int(Buffer *, u_int); u_int64_t buffer_get_int64(Buffer *); void buffer_put_int64(Buffer *, u_int64_t); int buffer_get_char(Buffer *); void buffer_put_char(Buffer *, int); void *buffer_get_string(Buffer *, u_int *); void *buffer_get_string_ptr(Buffer *, u_int *); void buffer_put_string(Buffer *, const void *, u_int); char *buffer_get_cstring(Buffer *, u_int *); void buffer_put_cstring(Buffer *, const char *); #define buffer_skip_string(b) \ do { u_int l = buffer_get_int(b); buffer_consume(b, l); } while (0) int buffer_put_bignum_ret(Buffer *, const BIGNUM *); int buffer_get_bignum_ret(Buffer *, BIGNUM *); int buffer_put_bignum2_ret(Buffer *, const BIGNUM *); int buffer_get_bignum2_ret(Buffer *, BIGNUM *); int buffer_get_short_ret(u_short *, Buffer *); int buffer_get_int_ret(u_int *, Buffer *); int buffer_get_int64_ret(u_int64_t *, Buffer *); void *buffer_get_string_ret(Buffer *, u_int *); char *buffer_get_cstring_ret(Buffer *, u_int *); void *buffer_get_string_ptr_ret(Buffer *, u_int *); int buffer_get_char_ret(char *, Buffer *); #ifdef OPENSSL_HAS_ECC #include int buffer_put_ecpoint_ret(Buffer *, const EC_GROUP *, const EC_POINT *); void buffer_put_ecpoint(Buffer *, const EC_GROUP *, const EC_POINT *); int buffer_get_ecpoint_ret(Buffer *, const EC_GROUP *, EC_POINT *); void buffer_get_ecpoint(Buffer *, const EC_GROUP *, EC_POINT *); #endif #endif /* BUFFER_H */ diff --git a/crypto/openssh/channels.c b/crypto/openssh/channels.c index 6abe2d012730..bf7072a1c0e6 100644 --- a/crypto/openssh/channels.c +++ b/crypto/openssh/channels.c @@ -1,3642 +1,3718 @@ /* $OpenBSD: channels.c,v 1.310 2010/11/24 01:24:14 djm Exp $ */ +/* $FreeBSD$ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved * This file contains functions for generic socket connection forwarding. * There is also code for initiating connection forwarding for X11 connections, * arbitrary tcp/ip connections, and the authentication agent connection. * * As far as I am concerned, the code I have written for this software * can be used freely for any purpose. Any derived versions of this * software must be clearly marked as such, and if the derived work is * incompatible with the protocol description in the RFC file, it must be * called by a name other than "ssh" or "Secure Shell". * * SSH2 support added by Markus Friedl. * Copyright (c) 1999, 2000, 2001, 2002 Markus Friedl. All rights reserved. * Copyright (c) 1999 Dug Song. All rights reserved. * Copyright (c) 1999 Theo de Raadt. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "includes.h" #include #include #include #include #ifdef HAVE_SYS_TIME_H # include #endif #include #include #include #include #include #include #include #include #include #include #include #include "openbsd-compat/sys-queue.h" #include "xmalloc.h" #include "ssh.h" #include "ssh1.h" #include "ssh2.h" #include "packet.h" #include "log.h" #include "misc.h" #include "buffer.h" #include "channels.h" #include "compat.h" #include "canohost.h" #include "key.h" #include "authfd.h" #include "pathnames.h" /* -- channel core */ /* * Pointer to an array containing all allocated channels. The array is * dynamically extended as needed. */ static Channel **channels = NULL; /* * Size of the channel array. All slots of the array must always be * initialized (at least the type field); unused slots set to NULL */ static u_int channels_alloc = 0; /* * Maximum file descriptor value used in any of the channels. This is * updated in channel_new. */ static int channel_max_fd = 0; /* -- tcp forwarding */ /* * Data structure for storing which hosts are permitted for forward requests. * The local sides of any remote forwards are stored in this array to prevent * a corrupt remote server from accessing arbitrary TCP/IP ports on our local * network (which might be behind a firewall). */ typedef struct { char *host_to_connect; /* Connect to 'host'. */ u_short port_to_connect; /* Connect to 'port'. */ u_short listen_port; /* Remote side should listen port number. */ } ForwardPermission; /* List of all permitted host/port pairs to connect by the user. */ static ForwardPermission *permitted_opens = NULL; /* List of all permitted host/port pairs to connect by the admin. */ static ForwardPermission *permitted_adm_opens = NULL; /* Number of permitted host/port pairs in the array permitted by the user. */ static int num_permitted_opens = 0; /* Number of permitted host/port pair in the array permitted by the admin. */ static int num_adm_permitted_opens = 0; /* * If this is true, all opens are permitted. This is the case on the server * on which we have to trust the client anyway, and the user could do * anything after logging in anyway. */ static int all_opens_permitted = 0; /* -- X11 forwarding */ /* Maximum number of fake X11 displays to try. */ #define MAX_DISPLAYS 1000 /* Saved X11 local (client) display. */ static char *x11_saved_display = NULL; /* Saved X11 authentication protocol name. */ static char *x11_saved_proto = NULL; /* Saved X11 authentication data. This is the real data. */ static char *x11_saved_data = NULL; static u_int x11_saved_data_len = 0; /* * Fake X11 authentication data. This is what the server will be sending us; * we should replace any occurrences of this by the real data. */ static u_char *x11_fake_data = NULL; static u_int x11_fake_data_len; /* -- agent forwarding */ #define NUM_SOCKS 10 /* AF_UNSPEC or AF_INET or AF_INET6 */ static int IPv4or6 = AF_UNSPEC; /* helper */ static void port_open_helper(Channel *c, char *rtype); /* non-blocking connect helpers */ static int connect_next(struct channel_connect *); static void channel_connect_ctx_free(struct channel_connect *); +/* -- HPN */ + +static int hpn_disabled = 0; +static u_int buffer_size = CHAN_HPN_MIN_WINDOW_DEFAULT; + /* -- channel core */ Channel * channel_by_id(int id) { Channel *c; if (id < 0 || (u_int)id >= channels_alloc) { logit("channel_by_id: %d: bad id", id); return NULL; } c = channels[id]; if (c == NULL) { logit("channel_by_id: %d: bad id: channel free", id); return NULL; } return c; } /* * Returns the channel if it is allowed to receive protocol messages. * Private channels, like listening sockets, may not receive messages. */ Channel * channel_lookup(int id) { Channel *c; if ((c = channel_by_id(id)) == NULL) return (NULL); switch (c->type) { case SSH_CHANNEL_X11_OPEN: case SSH_CHANNEL_LARVAL: case SSH_CHANNEL_CONNECTING: case SSH_CHANNEL_DYNAMIC: case SSH_CHANNEL_OPENING: case SSH_CHANNEL_OPEN: case SSH_CHANNEL_INPUT_DRAINING: case SSH_CHANNEL_OUTPUT_DRAINING: return (c); } logit("Non-public channel %d, type %d.", id, c->type); return (NULL); } /* * Register filedescriptors for a channel, used when allocating a channel or * when the channel consumer/producer is ready, e.g. shell exec'd */ static void channel_register_fds(Channel *c, int rfd, int wfd, int efd, int extusage, int nonblock, int is_tty) { /* Update the maximum file descriptor value. */ channel_max_fd = MAX(channel_max_fd, rfd); channel_max_fd = MAX(channel_max_fd, wfd); channel_max_fd = MAX(channel_max_fd, efd); if (rfd != -1) fcntl(rfd, F_SETFD, FD_CLOEXEC); if (wfd != -1 && wfd != rfd) fcntl(wfd, F_SETFD, FD_CLOEXEC); if (efd != -1 && efd != rfd && efd != wfd) fcntl(efd, F_SETFD, FD_CLOEXEC); c->rfd = rfd; c->wfd = wfd; c->sock = (rfd == wfd) ? rfd : -1; c->efd = efd; c->extended_usage = extusage; if ((c->isatty = is_tty) != 0) debug2("channel %d: rfd %d isatty", c->self, c->rfd); c->wfd_isatty = is_tty || isatty(c->wfd); /* enable nonblocking mode */ if (nonblock) { if (rfd != -1) set_nonblock(rfd); if (wfd != -1) set_nonblock(wfd); if (efd != -1) set_nonblock(efd); } } /* * Allocate a new channel object and set its type and socket. This will cause * remote_name to be freed. */ Channel * channel_new(char *ctype, int type, int rfd, int wfd, int efd, u_int window, u_int maxpack, int extusage, char *remote_name, int nonblock) { int found; u_int i; Channel *c; /* Do initial allocation if this is the first call. */ if (channels_alloc == 0) { channels_alloc = 10; channels = xcalloc(channels_alloc, sizeof(Channel *)); for (i = 0; i < channels_alloc; i++) channels[i] = NULL; } /* Try to find a free slot where to put the new channel. */ for (found = -1, i = 0; i < channels_alloc; i++) if (channels[i] == NULL) { /* Found a free slot. */ found = (int)i; break; } if (found < 0) { /* There are no free slots. Take last+1 slot and expand the array. */ found = channels_alloc; if (channels_alloc > 10000) fatal("channel_new: internal error: channels_alloc %d " "too big.", channels_alloc); channels = xrealloc(channels, channels_alloc + 10, sizeof(Channel *)); channels_alloc += 10; debug2("channel: expanding %d", channels_alloc); for (i = found; i < channels_alloc; i++) channels[i] = NULL; } /* Initialize and return new channel. */ c = channels[found] = xcalloc(1, sizeof(Channel)); buffer_init(&c->input); buffer_init(&c->output); buffer_init(&c->extended); c->path = NULL; c->ostate = CHAN_OUTPUT_OPEN; c->istate = CHAN_INPUT_OPEN; c->flags = 0; channel_register_fds(c, rfd, wfd, efd, extusage, nonblock, 0); c->self = found; c->type = type; c->ctype = ctype; + c->dynamic_window = 0; c->local_window = window; c->local_window_max = window; c->local_consumed = 0; c->local_maxpacket = maxpack; c->remote_id = -1; c->remote_name = xstrdup(remote_name); c->remote_window = 0; c->remote_maxpacket = 0; c->force_drain = 0; c->single_connection = 0; c->detach_user = NULL; c->detach_close = 0; c->open_confirm = NULL; c->open_confirm_ctx = NULL; c->input_filter = NULL; c->output_filter = NULL; c->filter_ctx = NULL; c->filter_cleanup = NULL; c->ctl_chan = -1; c->mux_rcb = NULL; c->mux_ctx = NULL; c->mux_pause = 0; c->delayed = 1; /* prevent call to channel_post handler */ TAILQ_INIT(&c->status_confirms); debug("channel %d: new [%s]", found, remote_name); return c; } static int channel_find_maxfd(void) { u_int i; int max = 0; Channel *c; for (i = 0; i < channels_alloc; i++) { c = channels[i]; if (c != NULL) { max = MAX(max, c->rfd); max = MAX(max, c->wfd); max = MAX(max, c->efd); } } return max; } int channel_close_fd(int *fdp) { int ret = 0, fd = *fdp; if (fd != -1) { ret = close(fd); *fdp = -1; if (fd == channel_max_fd) channel_max_fd = channel_find_maxfd(); } return ret; } /* Close all channel fd/socket. */ static void channel_close_fds(Channel *c) { channel_close_fd(&c->sock); channel_close_fd(&c->rfd); channel_close_fd(&c->wfd); channel_close_fd(&c->efd); } /* Free the channel and close its fd/socket. */ void channel_free(Channel *c) { char *s; u_int i, n; struct channel_confirm *cc; for (n = 0, i = 0; i < channels_alloc; i++) if (channels[i]) n++; debug("channel %d: free: %s, nchannels %u", c->self, c->remote_name ? c->remote_name : "???", n); s = channel_open_message(); debug3("channel %d: status: %s", c->self, s); xfree(s); if (c->sock != -1) shutdown(c->sock, SHUT_RDWR); channel_close_fds(c); buffer_free(&c->input); buffer_free(&c->output); buffer_free(&c->extended); if (c->remote_name) { xfree(c->remote_name); c->remote_name = NULL; } if (c->path) { xfree(c->path); c->path = NULL; } while ((cc = TAILQ_FIRST(&c->status_confirms)) != NULL) { if (cc->abandon_cb != NULL) cc->abandon_cb(c, cc->ctx); TAILQ_REMOVE(&c->status_confirms, cc, entry); bzero(cc, sizeof(*cc)); xfree(cc); } if (c->filter_cleanup != NULL && c->filter_ctx != NULL) c->filter_cleanup(c->self, c->filter_ctx); channels[c->self] = NULL; xfree(c); } void channel_free_all(void) { u_int i; for (i = 0; i < channels_alloc; i++) if (channels[i] != NULL) channel_free(channels[i]); } /* * Closes the sockets/fds of all channels. This is used to close extra file * descriptors after a fork. */ void channel_close_all(void) { u_int i; for (i = 0; i < channels_alloc; i++) if (channels[i] != NULL) channel_close_fds(channels[i]); } /* * Stop listening to channels. */ void channel_stop_listening(void) { u_int i; Channel *c; for (i = 0; i < channels_alloc; i++) { c = channels[i]; if (c != NULL) { switch (c->type) { case SSH_CHANNEL_AUTH_SOCKET: case SSH_CHANNEL_PORT_LISTENER: case SSH_CHANNEL_RPORT_LISTENER: case SSH_CHANNEL_X11_LISTENER: channel_close_fd(&c->sock); channel_free(c); break; } } } } /* * Returns true if no channel has too much buffered data, and false if one or * more channel is overfull. */ int channel_not_very_much_buffered_data(void) { u_int i; Channel *c; for (i = 0; i < channels_alloc; i++) { c = channels[i]; if (c != NULL && c->type == SSH_CHANNEL_OPEN) { #if 0 if (!compat20 && buffer_len(&c->input) > packet_get_maxsize()) { debug2("channel %d: big input buffer %d", c->self, buffer_len(&c->input)); return 0; } #endif if (buffer_len(&c->output) > packet_get_maxsize()) { debug2("channel %d: big output buffer %u > %u", c->self, buffer_len(&c->output), packet_get_maxsize()); return 0; } } } return 1; } /* Returns true if any channel is still open. */ int channel_still_open(void) { u_int i; Channel *c; for (i = 0; i < channels_alloc; i++) { c = channels[i]; if (c == NULL) continue; switch (c->type) { case SSH_CHANNEL_X11_LISTENER: case SSH_CHANNEL_PORT_LISTENER: case SSH_CHANNEL_RPORT_LISTENER: case SSH_CHANNEL_MUX_LISTENER: case SSH_CHANNEL_CLOSED: case SSH_CHANNEL_AUTH_SOCKET: case SSH_CHANNEL_DYNAMIC: case SSH_CHANNEL_CONNECTING: case SSH_CHANNEL_ZOMBIE: continue; case SSH_CHANNEL_LARVAL: if (!compat20) fatal("cannot happen: SSH_CHANNEL_LARVAL"); continue; case SSH_CHANNEL_OPENING: case SSH_CHANNEL_OPEN: case SSH_CHANNEL_X11_OPEN: case SSH_CHANNEL_MUX_CLIENT: return 1; case SSH_CHANNEL_INPUT_DRAINING: case SSH_CHANNEL_OUTPUT_DRAINING: if (!compat13) fatal("cannot happen: OUT_DRAIN"); return 1; default: fatal("channel_still_open: bad channel type %d", c->type); /* NOTREACHED */ } } return 0; } /* Returns the id of an open channel suitable for keepaliving */ int channel_find_open(void) { u_int i; Channel *c; for (i = 0; i < channels_alloc; i++) { c = channels[i]; if (c == NULL || c->remote_id < 0) continue; switch (c->type) { case SSH_CHANNEL_CLOSED: case SSH_CHANNEL_DYNAMIC: case SSH_CHANNEL_X11_LISTENER: case SSH_CHANNEL_PORT_LISTENER: case SSH_CHANNEL_RPORT_LISTENER: case SSH_CHANNEL_MUX_LISTENER: case SSH_CHANNEL_MUX_CLIENT: case SSH_CHANNEL_OPENING: case SSH_CHANNEL_CONNECTING: case SSH_CHANNEL_ZOMBIE: continue; case SSH_CHANNEL_LARVAL: case SSH_CHANNEL_AUTH_SOCKET: case SSH_CHANNEL_OPEN: case SSH_CHANNEL_X11_OPEN: return i; case SSH_CHANNEL_INPUT_DRAINING: case SSH_CHANNEL_OUTPUT_DRAINING: if (!compat13) fatal("cannot happen: OUT_DRAIN"); return i; default: fatal("channel_find_open: bad channel type %d", c->type); /* NOTREACHED */ } } return -1; } /* * Returns a message describing the currently open forwarded connections, * suitable for sending to the client. The message contains crlf pairs for * newlines. */ char * channel_open_message(void) { Buffer buffer; Channel *c; char buf[1024], *cp; u_int i; buffer_init(&buffer); snprintf(buf, sizeof buf, "The following connections are open:\r\n"); buffer_append(&buffer, buf, strlen(buf)); for (i = 0; i < channels_alloc; i++) { c = channels[i]; if (c == NULL) continue; switch (c->type) { case SSH_CHANNEL_X11_LISTENER: case SSH_CHANNEL_PORT_LISTENER: case SSH_CHANNEL_RPORT_LISTENER: case SSH_CHANNEL_CLOSED: case SSH_CHANNEL_AUTH_SOCKET: case SSH_CHANNEL_ZOMBIE: case SSH_CHANNEL_MUX_CLIENT: case SSH_CHANNEL_MUX_LISTENER: continue; case SSH_CHANNEL_LARVAL: case SSH_CHANNEL_OPENING: case SSH_CHANNEL_CONNECTING: case SSH_CHANNEL_DYNAMIC: case SSH_CHANNEL_OPEN: case SSH_CHANNEL_X11_OPEN: case SSH_CHANNEL_INPUT_DRAINING: case SSH_CHANNEL_OUTPUT_DRAINING: snprintf(buf, sizeof buf, " #%d %.300s (t%d r%d i%d/%d o%d/%d fd %d/%d cc %d)\r\n", c->self, c->remote_name, c->type, c->remote_id, c->istate, buffer_len(&c->input), c->ostate, buffer_len(&c->output), c->rfd, c->wfd, c->ctl_chan); buffer_append(&buffer, buf, strlen(buf)); continue; default: fatal("channel_open_message: bad channel type %d", c->type); /* NOTREACHED */ } } buffer_append(&buffer, "\0", 1); cp = xstrdup(buffer_ptr(&buffer)); buffer_free(&buffer); return cp; } void channel_send_open(int id) { Channel *c = channel_lookup(id); if (c == NULL) { logit("channel_send_open: %d: bad id", id); return; } debug2("channel %d: send open", id); packet_start(SSH2_MSG_CHANNEL_OPEN); packet_put_cstring(c->ctype); packet_put_int(c->self); packet_put_int(c->local_window); packet_put_int(c->local_maxpacket); packet_send(); } void channel_request_start(int id, char *service, int wantconfirm) { Channel *c = channel_lookup(id); if (c == NULL) { logit("channel_request_start: %d: unknown channel id", id); return; } debug2("channel %d: request %s confirm %d", id, service, wantconfirm); packet_start(SSH2_MSG_CHANNEL_REQUEST); packet_put_int(c->remote_id); packet_put_cstring(service); packet_put_char(wantconfirm); } void channel_register_status_confirm(int id, channel_confirm_cb *cb, channel_confirm_abandon_cb *abandon_cb, void *ctx) { struct channel_confirm *cc; Channel *c; if ((c = channel_lookup(id)) == NULL) fatal("channel_register_expect: %d: bad id", id); cc = xmalloc(sizeof(*cc)); cc->cb = cb; cc->abandon_cb = abandon_cb; cc->ctx = ctx; TAILQ_INSERT_TAIL(&c->status_confirms, cc, entry); } void channel_register_open_confirm(int id, channel_open_fn *fn, void *ctx) { Channel *c = channel_lookup(id); if (c == NULL) { logit("channel_register_open_confirm: %d: bad id", id); return; } c->open_confirm = fn; c->open_confirm_ctx = ctx; } void channel_register_cleanup(int id, channel_callback_fn *fn, int do_close) { Channel *c = channel_by_id(id); if (c == NULL) { logit("channel_register_cleanup: %d: bad id", id); return; } c->detach_user = fn; c->detach_close = do_close; } void channel_cancel_cleanup(int id) { Channel *c = channel_by_id(id); if (c == NULL) { logit("channel_cancel_cleanup: %d: bad id", id); return; } c->detach_user = NULL; c->detach_close = 0; } void channel_register_filter(int id, channel_infilter_fn *ifn, channel_outfilter_fn *ofn, channel_filter_cleanup_fn *cfn, void *ctx) { Channel *c = channel_lookup(id); if (c == NULL) { logit("channel_register_filter: %d: bad id", id); return; } c->input_filter = ifn; c->output_filter = ofn; c->filter_ctx = ctx; c->filter_cleanup = cfn; } void channel_set_fds(int id, int rfd, int wfd, int efd, int extusage, int nonblock, int is_tty, u_int window_max) { Channel *c = channel_lookup(id); if (c == NULL || c->type != SSH_CHANNEL_LARVAL) fatal("channel_activate for non-larval channel %d.", id); channel_register_fds(c, rfd, wfd, efd, extusage, nonblock, is_tty); c->type = SSH_CHANNEL_OPEN; c->local_window = c->local_window_max = window_max; packet_start(SSH2_MSG_CHANNEL_WINDOW_ADJUST); packet_put_int(c->remote_id); packet_put_int(c->local_window); packet_send(); } /* * 'channel_pre*' are called just before select() to add any bits relevant to * channels in the select bitmasks. */ /* * 'channel_post*': perform any appropriate operations for channels which * have events pending. */ typedef void chan_fn(Channel *c, fd_set *readset, fd_set *writeset); chan_fn *channel_pre[SSH_CHANNEL_MAX_TYPE]; chan_fn *channel_post[SSH_CHANNEL_MAX_TYPE]; /* ARGSUSED */ static void channel_pre_listener(Channel *c, fd_set *readset, fd_set *writeset) { FD_SET(c->sock, readset); } /* ARGSUSED */ static void channel_pre_connecting(Channel *c, fd_set *readset, fd_set *writeset) { debug3("channel %d: waiting for connection", c->self); FD_SET(c->sock, writeset); } static void channel_pre_open_13(Channel *c, fd_set *readset, fd_set *writeset) { if (buffer_len(&c->input) < packet_get_maxsize()) FD_SET(c->sock, readset); if (buffer_len(&c->output) > 0) FD_SET(c->sock, writeset); } +static u_int +channel_tcpwinsz(void) +{ + u_int32_t tcpwinsz; + socklen_t optsz; + int ret, sd; + u_int maxlen; + + /* If we are not on a socket return 128KB. */ + if (!packet_connection_is_on_socket()) + return (128 * 1024); + + tcpwinsz = 0; + optsz = sizeof(tcpwinsz); + sd = packet_get_connection_in(); + ret = getsockopt(sd, SOL_SOCKET, SO_RCVBUF, &tcpwinsz, &optsz); + + /* Return no more than the maximum buffer size. */ + maxlen = buffer_get_max_len(); + if ((ret == 0) && tcpwinsz > maxlen) + tcpwinsz = maxlen; + /* In case getsockopt() failed return a minimum. */ + if (tcpwinsz == 0) + tcpwinsz = CHAN_TCP_WINDOW_DEFAULT; + debug2("tcpwinsz: %d for connection: %d", tcpwinsz, sd); + return (tcpwinsz); +} + static void channel_pre_open(Channel *c, fd_set *readset, fd_set *writeset) { - u_int limit = compat20 ? c->remote_window : packet_get_maxsize(); + u_int limit; + + /* Check buffer limits. */ + if (!c->tcpwinsz || c->dynamic_window > 0) + c->tcpwinsz = channel_tcpwinsz(); + limit = MIN(compat20 ? c->remote_window : packet_get_maxsize(), + 2 * c->tcpwinsz); + if (c->istate == CHAN_INPUT_OPEN && limit > 0 && buffer_len(&c->input) < limit && buffer_check_alloc(&c->input, CHAN_RBUF)) FD_SET(c->rfd, readset); if (c->ostate == CHAN_OUTPUT_OPEN || c->ostate == CHAN_OUTPUT_WAIT_DRAIN) { if (buffer_len(&c->output) > 0) { FD_SET(c->wfd, writeset); } else if (c->ostate == CHAN_OUTPUT_WAIT_DRAIN) { if (CHANNEL_EFD_OUTPUT_ACTIVE(c)) debug2("channel %d: obuf_empty delayed efd %d/(%d)", c->self, c->efd, buffer_len(&c->extended)); else chan_obuf_empty(c); } } /** XXX check close conditions, too */ if (compat20 && c->efd != -1 && !(c->istate == CHAN_INPUT_CLOSED && c->ostate == CHAN_OUTPUT_CLOSED)) { if (c->extended_usage == CHAN_EXTENDED_WRITE && buffer_len(&c->extended) > 0) FD_SET(c->efd, writeset); else if (c->efd != -1 && !(c->flags & CHAN_EOF_SENT) && (c->extended_usage == CHAN_EXTENDED_READ || c->extended_usage == CHAN_EXTENDED_IGNORE) && buffer_len(&c->extended) < c->remote_window) FD_SET(c->efd, readset); } /* XXX: What about efd? races? */ } /* ARGSUSED */ static void channel_pre_input_draining(Channel *c, fd_set *readset, fd_set *writeset) { if (buffer_len(&c->input) == 0) { packet_start(SSH_MSG_CHANNEL_CLOSE); packet_put_int(c->remote_id); packet_send(); c->type = SSH_CHANNEL_CLOSED; debug2("channel %d: closing after input drain.", c->self); } } /* ARGSUSED */ static void channel_pre_output_draining(Channel *c, fd_set *readset, fd_set *writeset) { if (buffer_len(&c->output) == 0) chan_mark_dead(c); else FD_SET(c->sock, writeset); } /* * This is a special state for X11 authentication spoofing. An opened X11 * connection (when authentication spoofing is being done) remains in this * state until the first packet has been completely read. The authentication * data in that packet is then substituted by the real data if it matches the * fake data, and the channel is put into normal mode. * XXX All this happens at the client side. * Returns: 0 = need more data, -1 = wrong cookie, 1 = ok */ static int x11_open_helper(Buffer *b) { u_char *ucp; u_int proto_len, data_len; /* Check if the fixed size part of the packet is in buffer. */ if (buffer_len(b) < 12) return 0; /* Parse the lengths of variable-length fields. */ ucp = buffer_ptr(b); if (ucp[0] == 0x42) { /* Byte order MSB first. */ proto_len = 256 * ucp[6] + ucp[7]; data_len = 256 * ucp[8] + ucp[9]; } else if (ucp[0] == 0x6c) { /* Byte order LSB first. */ proto_len = ucp[6] + 256 * ucp[7]; data_len = ucp[8] + 256 * ucp[9]; } else { debug2("Initial X11 packet contains bad byte order byte: 0x%x", ucp[0]); return -1; } /* Check if the whole packet is in buffer. */ if (buffer_len(b) < 12 + ((proto_len + 3) & ~3) + ((data_len + 3) & ~3)) return 0; /* Check if authentication protocol matches. */ if (proto_len != strlen(x11_saved_proto) || memcmp(ucp + 12, x11_saved_proto, proto_len) != 0) { debug2("X11 connection uses different authentication protocol."); return -1; } /* Check if authentication data matches our fake data. */ if (data_len != x11_fake_data_len || timingsafe_bcmp(ucp + 12 + ((proto_len + 3) & ~3), x11_fake_data, x11_fake_data_len) != 0) { debug2("X11 auth data does not match fake data."); return -1; } /* Check fake data length */ if (x11_fake_data_len != x11_saved_data_len) { error("X11 fake_data_len %d != saved_data_len %d", x11_fake_data_len, x11_saved_data_len); return -1; } /* * Received authentication protocol and data match * our fake data. Substitute the fake data with real * data. */ memcpy(ucp + 12 + ((proto_len + 3) & ~3), x11_saved_data, x11_saved_data_len); return 1; } static void channel_pre_x11_open_13(Channel *c, fd_set *readset, fd_set *writeset) { int ret = x11_open_helper(&c->output); if (ret == 1) { /* Start normal processing for the channel. */ c->type = SSH_CHANNEL_OPEN; channel_pre_open_13(c, readset, writeset); } else if (ret == -1) { /* * We have received an X11 connection that has bad * authentication information. */ logit("X11 connection rejected because of wrong authentication."); buffer_clear(&c->input); buffer_clear(&c->output); channel_close_fd(&c->sock); c->sock = -1; c->type = SSH_CHANNEL_CLOSED; packet_start(SSH_MSG_CHANNEL_CLOSE); packet_put_int(c->remote_id); packet_send(); } } static void channel_pre_x11_open(Channel *c, fd_set *readset, fd_set *writeset) { int ret = x11_open_helper(&c->output); /* c->force_drain = 1; */ if (ret == 1) { c->type = SSH_CHANNEL_OPEN; channel_pre_open(c, readset, writeset); } else if (ret == -1) { logit("X11 connection rejected because of wrong authentication."); debug2("X11 rejected %d i%d/o%d", c->self, c->istate, c->ostate); chan_read_failed(c); buffer_clear(&c->input); chan_ibuf_empty(c); buffer_clear(&c->output); /* for proto v1, the peer will send an IEOF */ if (compat20) chan_write_failed(c); else c->type = SSH_CHANNEL_OPEN; debug2("X11 closed %d i%d/o%d", c->self, c->istate, c->ostate); } } static void channel_pre_mux_client(Channel *c, fd_set *readset, fd_set *writeset) { if (c->istate == CHAN_INPUT_OPEN && !c->mux_pause && buffer_check_alloc(&c->input, CHAN_RBUF)) FD_SET(c->rfd, readset); if (c->istate == CHAN_INPUT_WAIT_DRAIN) { /* clear buffer immediately (discard any partial packet) */ buffer_clear(&c->input); chan_ibuf_empty(c); /* Start output drain. XXX just kill chan? */ chan_rcvd_oclose(c); } if (c->ostate == CHAN_OUTPUT_OPEN || c->ostate == CHAN_OUTPUT_WAIT_DRAIN) { if (buffer_len(&c->output) > 0) FD_SET(c->wfd, writeset); else if (c->ostate == CHAN_OUTPUT_WAIT_DRAIN) chan_obuf_empty(c); } } /* try to decode a socks4 header */ /* ARGSUSED */ static int channel_decode_socks4(Channel *c, fd_set *readset, fd_set *writeset) { char *p, *host; u_int len, have, i, found, need; char username[256]; struct { u_int8_t version; u_int8_t command; u_int16_t dest_port; struct in_addr dest_addr; } s4_req, s4_rsp; debug2("channel %d: decode socks4", c->self); have = buffer_len(&c->input); len = sizeof(s4_req); if (have < len) return 0; p = buffer_ptr(&c->input); need = 1; /* SOCKS4A uses an invalid IP address 0.0.0.x */ if (p[4] == 0 && p[5] == 0 && p[6] == 0 && p[7] != 0) { debug2("channel %d: socks4a request", c->self); /* ... and needs an extra string (the hostname) */ need = 2; } /* Check for terminating NUL on the string(s) */ for (found = 0, i = len; i < have; i++) { if (p[i] == '\0') { found++; if (found == need) break; } if (i > 1024) { /* the peer is probably sending garbage */ debug("channel %d: decode socks4: too long", c->self); return -1; } } if (found < need) return 0; buffer_get(&c->input, (char *)&s4_req.version, 1); buffer_get(&c->input, (char *)&s4_req.command, 1); buffer_get(&c->input, (char *)&s4_req.dest_port, 2); buffer_get(&c->input, (char *)&s4_req.dest_addr, 4); have = buffer_len(&c->input); p = buffer_ptr(&c->input); len = strlen(p); debug2("channel %d: decode socks4: user %s/%d", c->self, p, len); len++; /* trailing '\0' */ if (len > have) fatal("channel %d: decode socks4: len %d > have %d", c->self, len, have); strlcpy(username, p, sizeof(username)); buffer_consume(&c->input, len); if (c->path != NULL) { xfree(c->path); c->path = NULL; } if (need == 1) { /* SOCKS4: one string */ host = inet_ntoa(s4_req.dest_addr); c->path = xstrdup(host); } else { /* SOCKS4A: two strings */ have = buffer_len(&c->input); p = buffer_ptr(&c->input); len = strlen(p); debug2("channel %d: decode socks4a: host %s/%d", c->self, p, len); len++; /* trailing '\0' */ if (len > have) fatal("channel %d: decode socks4a: len %d > have %d", c->self, len, have); if (len > NI_MAXHOST) { error("channel %d: hostname \"%.100s\" too long", c->self, p); return -1; } c->path = xstrdup(p); buffer_consume(&c->input, len); } c->host_port = ntohs(s4_req.dest_port); debug2("channel %d: dynamic request: socks4 host %s port %u command %u", c->self, c->path, c->host_port, s4_req.command); if (s4_req.command != 1) { debug("channel %d: cannot handle: %s cn %d", c->self, need == 1 ? "SOCKS4" : "SOCKS4A", s4_req.command); return -1; } s4_rsp.version = 0; /* vn: 0 for reply */ s4_rsp.command = 90; /* cd: req granted */ s4_rsp.dest_port = 0; /* ignored */ s4_rsp.dest_addr.s_addr = INADDR_ANY; /* ignored */ buffer_append(&c->output, &s4_rsp, sizeof(s4_rsp)); return 1; } /* try to decode a socks5 header */ #define SSH_SOCKS5_AUTHDONE 0x1000 #define SSH_SOCKS5_NOAUTH 0x00 #define SSH_SOCKS5_IPV4 0x01 #define SSH_SOCKS5_DOMAIN 0x03 #define SSH_SOCKS5_IPV6 0x04 #define SSH_SOCKS5_CONNECT 0x01 #define SSH_SOCKS5_SUCCESS 0x00 /* ARGSUSED */ static int channel_decode_socks5(Channel *c, fd_set *readset, fd_set *writeset) { struct { u_int8_t version; u_int8_t command; u_int8_t reserved; u_int8_t atyp; } s5_req, s5_rsp; u_int16_t dest_port; u_char *p, dest_addr[255+1], ntop[INET6_ADDRSTRLEN]; u_int have, need, i, found, nmethods, addrlen, af; debug2("channel %d: decode socks5", c->self); p = buffer_ptr(&c->input); if (p[0] != 0x05) return -1; have = buffer_len(&c->input); if (!(c->flags & SSH_SOCKS5_AUTHDONE)) { /* format: ver | nmethods | methods */ if (have < 2) return 0; nmethods = p[1]; if (have < nmethods + 2) return 0; /* look for method: "NO AUTHENTICATION REQUIRED" */ for (found = 0, i = 2; i < nmethods + 2; i++) { if (p[i] == SSH_SOCKS5_NOAUTH) { found = 1; break; } } if (!found) { debug("channel %d: method SSH_SOCKS5_NOAUTH not found", c->self); return -1; } buffer_consume(&c->input, nmethods + 2); buffer_put_char(&c->output, 0x05); /* version */ buffer_put_char(&c->output, SSH_SOCKS5_NOAUTH); /* method */ FD_SET(c->sock, writeset); c->flags |= SSH_SOCKS5_AUTHDONE; debug2("channel %d: socks5 auth done", c->self); return 0; /* need more */ } debug2("channel %d: socks5 post auth", c->self); if (have < sizeof(s5_req)+1) return 0; /* need more */ memcpy(&s5_req, p, sizeof(s5_req)); if (s5_req.version != 0x05 || s5_req.command != SSH_SOCKS5_CONNECT || s5_req.reserved != 0x00) { debug2("channel %d: only socks5 connect supported", c->self); return -1; } switch (s5_req.atyp){ case SSH_SOCKS5_IPV4: addrlen = 4; af = AF_INET; break; case SSH_SOCKS5_DOMAIN: addrlen = p[sizeof(s5_req)]; af = -1; break; case SSH_SOCKS5_IPV6: addrlen = 16; af = AF_INET6; break; default: debug2("channel %d: bad socks5 atyp %d", c->self, s5_req.atyp); return -1; } need = sizeof(s5_req) + addrlen + 2; if (s5_req.atyp == SSH_SOCKS5_DOMAIN) need++; if (have < need) return 0; buffer_consume(&c->input, sizeof(s5_req)); if (s5_req.atyp == SSH_SOCKS5_DOMAIN) buffer_consume(&c->input, 1); /* host string length */ buffer_get(&c->input, (char *)&dest_addr, addrlen); buffer_get(&c->input, (char *)&dest_port, 2); dest_addr[addrlen] = '\0'; if (c->path != NULL) { xfree(c->path); c->path = NULL; } if (s5_req.atyp == SSH_SOCKS5_DOMAIN) { if (addrlen >= NI_MAXHOST) { error("channel %d: dynamic request: socks5 hostname " "\"%.100s\" too long", c->self, dest_addr); return -1; } c->path = xstrdup(dest_addr); } else { if (inet_ntop(af, dest_addr, ntop, sizeof(ntop)) == NULL) return -1; c->path = xstrdup(ntop); } c->host_port = ntohs(dest_port); debug2("channel %d: dynamic request: socks5 host %s port %u command %u", c->self, c->path, c->host_port, s5_req.command); s5_rsp.version = 0x05; s5_rsp.command = SSH_SOCKS5_SUCCESS; s5_rsp.reserved = 0; /* ignored */ s5_rsp.atyp = SSH_SOCKS5_IPV4; ((struct in_addr *)&dest_addr)->s_addr = INADDR_ANY; dest_port = 0; /* ignored */ buffer_append(&c->output, &s5_rsp, sizeof(s5_rsp)); buffer_append(&c->output, &dest_addr, sizeof(struct in_addr)); buffer_append(&c->output, &dest_port, sizeof(dest_port)); return 1; } Channel * channel_connect_stdio_fwd(const char *host_to_connect, u_short port_to_connect, int in, int out) { Channel *c; debug("channel_connect_stdio_fwd %s:%d", host_to_connect, port_to_connect); c = channel_new("stdio-forward", SSH_CHANNEL_OPENING, in, out, -1, CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, 0, "stdio-forward", /*nonblock*/0); c->path = xstrdup(host_to_connect); c->host_port = port_to_connect; c->listening_port = 0; c->force_drain = 1; channel_register_fds(c, in, out, -1, 0, 1, 0); port_open_helper(c, "direct-tcpip"); return c; } /* dynamic port forwarding */ static void channel_pre_dynamic(Channel *c, fd_set *readset, fd_set *writeset) { u_char *p; u_int have; int ret; have = buffer_len(&c->input); debug2("channel %d: pre_dynamic: have %d", c->self, have); /* buffer_dump(&c->input); */ /* check if the fixed size part of the packet is in buffer. */ if (have < 3) { /* need more */ FD_SET(c->sock, readset); return; } /* try to guess the protocol */ p = buffer_ptr(&c->input); switch (p[0]) { case 0x04: ret = channel_decode_socks4(c, readset, writeset); break; case 0x05: ret = channel_decode_socks5(c, readset, writeset); break; default: ret = -1; break; } if (ret < 0) { chan_mark_dead(c); } else if (ret == 0) { debug2("channel %d: pre_dynamic: need more", c->self); /* need more */ FD_SET(c->sock, readset); } else { /* switch to the next state */ c->type = SSH_CHANNEL_OPENING; port_open_helper(c, "direct-tcpip"); } } /* This is our fake X11 server socket. */ /* ARGSUSED */ static void channel_post_x11_listener(Channel *c, fd_set *readset, fd_set *writeset) { Channel *nc; struct sockaddr_storage addr; int newsock; socklen_t addrlen; char buf[16384], *remote_ipaddr; int remote_port; if (FD_ISSET(c->sock, readset)) { debug("X11 connection requested."); addrlen = sizeof(addr); newsock = accept(c->sock, (struct sockaddr *)&addr, &addrlen); if (c->single_connection) { debug2("single_connection: closing X11 listener."); channel_close_fd(&c->sock); chan_mark_dead(c); } if (newsock < 0) { error("accept: %.100s", strerror(errno)); return; } set_nodelay(newsock); remote_ipaddr = get_peer_ipaddr(newsock); remote_port = get_peer_port(newsock); snprintf(buf, sizeof buf, "X11 connection from %.200s port %d", remote_ipaddr, remote_port); nc = channel_new("accepted x11 socket", SSH_CHANNEL_OPENING, newsock, newsock, -1, c->local_window_max, c->local_maxpacket, 0, buf, 1); if (compat20) { packet_start(SSH2_MSG_CHANNEL_OPEN); packet_put_cstring("x11"); packet_put_int(nc->self); packet_put_int(nc->local_window_max); packet_put_int(nc->local_maxpacket); /* originator ipaddr and port */ packet_put_cstring(remote_ipaddr); if (datafellows & SSH_BUG_X11FWD) { debug2("ssh2 x11 bug compat mode"); } else { packet_put_int(remote_port); } packet_send(); } else { packet_start(SSH_SMSG_X11_OPEN); packet_put_int(nc->self); if (packet_get_protocol_flags() & SSH_PROTOFLAG_HOST_IN_FWD_OPEN) packet_put_cstring(buf); packet_send(); } xfree(remote_ipaddr); } } static void port_open_helper(Channel *c, char *rtype) { int direct; char buf[1024]; char *remote_ipaddr = get_peer_ipaddr(c->sock); int remote_port = get_peer_port(c->sock); if (remote_port == -1) { /* Fake addr/port to appease peers that validate it (Tectia) */ xfree(remote_ipaddr); remote_ipaddr = xstrdup("127.0.0.1"); remote_port = 65535; } direct = (strcmp(rtype, "direct-tcpip") == 0); snprintf(buf, sizeof buf, "%s: listening port %d for %.100s port %d, " "connect from %.200s port %d", rtype, c->listening_port, c->path, c->host_port, remote_ipaddr, remote_port); xfree(c->remote_name); c->remote_name = xstrdup(buf); if (compat20) { packet_start(SSH2_MSG_CHANNEL_OPEN); packet_put_cstring(rtype); packet_put_int(c->self); packet_put_int(c->local_window_max); packet_put_int(c->local_maxpacket); if (direct) { /* target host, port */ packet_put_cstring(c->path); packet_put_int(c->host_port); } else { /* listen address, port */ packet_put_cstring(c->path); packet_put_int(c->listening_port); } /* originator host and port */ packet_put_cstring(remote_ipaddr); packet_put_int((u_int)remote_port); packet_send(); } else { packet_start(SSH_MSG_PORT_OPEN); packet_put_int(c->self); packet_put_cstring(c->path); packet_put_int(c->host_port); if (packet_get_protocol_flags() & SSH_PROTOFLAG_HOST_IN_FWD_OPEN) packet_put_cstring(c->remote_name); packet_send(); } xfree(remote_ipaddr); } static void channel_set_reuseaddr(int fd) { int on = 1; /* * Set socket options. * Allow local port reuse in TIME_WAIT. */ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) error("setsockopt SO_REUSEADDR fd %d: %s", fd, strerror(errno)); } /* * This socket is listening for connections to a forwarded TCP/IP port. */ /* ARGSUSED */ static void channel_post_port_listener(Channel *c, fd_set *readset, fd_set *writeset) { Channel *nc; struct sockaddr_storage addr; int newsock, nextstate; socklen_t addrlen; char *rtype; if (FD_ISSET(c->sock, readset)) { debug("Connection to port %d forwarding " "to %.100s port %d requested.", c->listening_port, c->path, c->host_port); if (c->type == SSH_CHANNEL_RPORT_LISTENER) { nextstate = SSH_CHANNEL_OPENING; rtype = "forwarded-tcpip"; } else { if (c->host_port == 0) { nextstate = SSH_CHANNEL_DYNAMIC; rtype = "dynamic-tcpip"; } else { nextstate = SSH_CHANNEL_OPENING; rtype = "direct-tcpip"; } } addrlen = sizeof(addr); newsock = accept(c->sock, (struct sockaddr *)&addr, &addrlen); if (newsock < 0) { error("accept: %.100s", strerror(errno)); return; } set_nodelay(newsock); nc = channel_new(rtype, nextstate, newsock, newsock, -1, c->local_window_max, c->local_maxpacket, 0, rtype, 1); nc->listening_port = c->listening_port; nc->host_port = c->host_port; if (c->path != NULL) nc->path = xstrdup(c->path); if (nextstate != SSH_CHANNEL_DYNAMIC) port_open_helper(nc, rtype); } } /* * This is the authentication agent socket listening for connections from * clients. */ /* ARGSUSED */ static void channel_post_auth_listener(Channel *c, fd_set *readset, fd_set *writeset) { Channel *nc; int newsock; struct sockaddr_storage addr; socklen_t addrlen; if (FD_ISSET(c->sock, readset)) { addrlen = sizeof(addr); newsock = accept(c->sock, (struct sockaddr *)&addr, &addrlen); if (newsock < 0) { error("accept from auth socket: %.100s", strerror(errno)); return; } nc = channel_new("accepted auth socket", SSH_CHANNEL_OPENING, newsock, newsock, -1, c->local_window_max, c->local_maxpacket, 0, "accepted auth socket", 1); if (compat20) { packet_start(SSH2_MSG_CHANNEL_OPEN); packet_put_cstring("auth-agent@openssh.com"); packet_put_int(nc->self); packet_put_int(c->local_window_max); packet_put_int(c->local_maxpacket); } else { packet_start(SSH_SMSG_AGENT_OPEN); packet_put_int(nc->self); } packet_send(); } } /* ARGSUSED */ static void channel_post_connecting(Channel *c, fd_set *readset, fd_set *writeset) { int err = 0, sock; socklen_t sz = sizeof(err); if (FD_ISSET(c->sock, writeset)) { if (getsockopt(c->sock, SOL_SOCKET, SO_ERROR, &err, &sz) < 0) { err = errno; error("getsockopt SO_ERROR failed"); } if (err == 0) { debug("channel %d: connected to %s port %d", c->self, c->connect_ctx.host, c->connect_ctx.port); channel_connect_ctx_free(&c->connect_ctx); c->type = SSH_CHANNEL_OPEN; if (compat20) { packet_start(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION); packet_put_int(c->remote_id); packet_put_int(c->self); packet_put_int(c->local_window); packet_put_int(c->local_maxpacket); } else { packet_start(SSH_MSG_CHANNEL_OPEN_CONFIRMATION); packet_put_int(c->remote_id); packet_put_int(c->self); } } else { debug("channel %d: connection failed: %s", c->self, strerror(err)); /* Try next address, if any */ if ((sock = connect_next(&c->connect_ctx)) > 0) { close(c->sock); c->sock = c->rfd = c->wfd = sock; channel_max_fd = channel_find_maxfd(); return; } /* Exhausted all addresses */ error("connect_to %.100s port %d: failed.", c->connect_ctx.host, c->connect_ctx.port); channel_connect_ctx_free(&c->connect_ctx); if (compat20) { packet_start(SSH2_MSG_CHANNEL_OPEN_FAILURE); packet_put_int(c->remote_id); packet_put_int(SSH2_OPEN_CONNECT_FAILED); if (!(datafellows & SSH_BUG_OPENFAILURE)) { packet_put_cstring(strerror(err)); packet_put_cstring(""); } } else { packet_start(SSH_MSG_CHANNEL_OPEN_FAILURE); packet_put_int(c->remote_id); } chan_mark_dead(c); } packet_send(); } } /* ARGSUSED */ static int channel_handle_rfd(Channel *c, fd_set *readset, fd_set *writeset) { char buf[CHAN_RBUF]; int len, force; force = c->isatty && c->detach_close && c->istate != CHAN_INPUT_CLOSED; if (c->rfd != -1 && (force || FD_ISSET(c->rfd, readset))) { errno = 0; len = read(c->rfd, buf, sizeof(buf)); if (len < 0 && (errno == EINTR || ((errno == EAGAIN || errno == EWOULDBLOCK) && !force))) return 1; #ifndef PTY_ZEROREAD if (len <= 0) { #else if ((!c->isatty && len <= 0) || (c->isatty && (len < 0 || (len == 0 && errno != 0)))) { #endif debug2("channel %d: read<=0 rfd %d len %d", c->self, c->rfd, len); if (c->type != SSH_CHANNEL_OPEN) { debug2("channel %d: not open", c->self); chan_mark_dead(c); return -1; } else if (compat13) { buffer_clear(&c->output); c->type = SSH_CHANNEL_INPUT_DRAINING; debug2("channel %d: input draining.", c->self); } else { chan_read_failed(c); } return -1; } if (c->input_filter != NULL) { if (c->input_filter(c, buf, len) == -1) { debug2("channel %d: filter stops", c->self); chan_read_failed(c); } } else if (c->datagram) { buffer_put_string(&c->input, buf, len); } else { buffer_append(&c->input, buf, len); } } return 1; } /* ARGSUSED */ static int channel_handle_wfd(Channel *c, fd_set *readset, fd_set *writeset) { struct termios tio; u_char *data = NULL, *buf; u_int dlen, olen = 0; int len; /* Send buffered output data to the socket. */ if (c->wfd != -1 && FD_ISSET(c->wfd, writeset) && buffer_len(&c->output) > 0) { olen = buffer_len(&c->output); if (c->output_filter != NULL) { if ((buf = c->output_filter(c, &data, &dlen)) == NULL) { debug2("channel %d: filter stops", c->self); if (c->type != SSH_CHANNEL_OPEN) chan_mark_dead(c); else chan_write_failed(c); return -1; } } else if (c->datagram) { buf = data = buffer_get_string(&c->output, &dlen); } else { buf = data = buffer_ptr(&c->output); dlen = buffer_len(&c->output); } if (c->datagram) { /* ignore truncated writes, datagrams might get lost */ len = write(c->wfd, buf, dlen); xfree(data); if (len < 0 && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)) return 1; if (len <= 0) { if (c->type != SSH_CHANNEL_OPEN) chan_mark_dead(c); else chan_write_failed(c); return -1; } goto out; } #ifdef _AIX /* XXX: Later AIX versions can't push as much data to tty */ if (compat20 && c->wfd_isatty) dlen = MIN(dlen, 8*1024); #endif len = write(c->wfd, buf, dlen); if (len < 0 && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)) return 1; if (len <= 0) { if (c->type != SSH_CHANNEL_OPEN) { debug2("channel %d: not open", c->self); chan_mark_dead(c); return -1; } else if (compat13) { buffer_clear(&c->output); debug2("channel %d: input draining.", c->self); c->type = SSH_CHANNEL_INPUT_DRAINING; } else { chan_write_failed(c); } return -1; } #ifndef BROKEN_TCGETATTR_ICANON if (compat20 && c->isatty && dlen >= 1 && buf[0] != '\r') { if (tcgetattr(c->wfd, &tio) == 0 && !(tio.c_lflag & ECHO) && (tio.c_lflag & ICANON)) { /* * Simulate echo to reduce the impact of * traffic analysis. We need to match the * size of a SSH2_MSG_CHANNEL_DATA message * (4 byte channel id + buf) */ packet_send_ignore(4 + len); packet_send(); } } #endif buffer_consume(&c->output, len); } out: if (compat20 && olen > 0) c->local_consumed += olen - buffer_len(&c->output); return 1; } static int channel_handle_efd(Channel *c, fd_set *readset, fd_set *writeset) { char buf[CHAN_RBUF]; int len; /** XXX handle drain efd, too */ if (c->efd != -1) { if (c->extended_usage == CHAN_EXTENDED_WRITE && FD_ISSET(c->efd, writeset) && buffer_len(&c->extended) > 0) { len = write(c->efd, buffer_ptr(&c->extended), buffer_len(&c->extended)); debug2("channel %d: written %d to efd %d", c->self, len, c->efd); if (len < 0 && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)) return 1; if (len <= 0) { debug2("channel %d: closing write-efd %d", c->self, c->efd); channel_close_fd(&c->efd); } else { buffer_consume(&c->extended, len); c->local_consumed += len; } } else if (c->efd != -1 && (c->extended_usage == CHAN_EXTENDED_READ || c->extended_usage == CHAN_EXTENDED_IGNORE) && (c->detach_close || FD_ISSET(c->efd, readset))) { len = read(c->efd, buf, sizeof(buf)); debug2("channel %d: read %d from efd %d", c->self, len, c->efd); if (len < 0 && (errno == EINTR || ((errno == EAGAIN || errno == EWOULDBLOCK) && !c->detach_close))) return 1; if (len <= 0) { debug2("channel %d: closing read-efd %d", c->self, c->efd); channel_close_fd(&c->efd); } else { if (c->extended_usage == CHAN_EXTENDED_IGNORE) { debug3("channel %d: discard efd", c->self); } else buffer_append(&c->extended, buf, len); } } } return 1; } static int channel_check_window(Channel *c) { if (c->type == SSH_CHANNEL_OPEN && !(c->flags & (CHAN_CLOSE_SENT|CHAN_CLOSE_RCVD)) && ((c->local_window_max - c->local_window > c->local_maxpacket*3) || c->local_window < c->local_window_max/2) && c->local_consumed > 0) { + u_int addition = 0; + + /* Adjust max window size if we are in a dynamic environment. */ + if (c->dynamic_window && c->tcpwinsz > c->local_window_max) { + /* + * Grow the window somewhat aggressively to maintain + * pressure. + */ + addition = 1.5 * (c->tcpwinsz - c->local_window_max); + c->local_window_max += addition; + } packet_start(SSH2_MSG_CHANNEL_WINDOW_ADJUST); packet_put_int(c->remote_id); - packet_put_int(c->local_consumed); + packet_put_int(c->local_consumed + addition); packet_send(); debug2("channel %d: window %d sent adjust %d", c->self, c->local_window, c->local_consumed); - c->local_window += c->local_consumed; + c->local_window += c->local_consumed + addition; c->local_consumed = 0; } return 1; } static void channel_post_open(Channel *c, fd_set *readset, fd_set *writeset) { channel_handle_rfd(c, readset, writeset); channel_handle_wfd(c, readset, writeset); if (!compat20) return; channel_handle_efd(c, readset, writeset); channel_check_window(c); } static u_int read_mux(Channel *c, u_int need) { char buf[CHAN_RBUF]; int len; u_int rlen; if (buffer_len(&c->input) < need) { rlen = need - buffer_len(&c->input); len = read(c->rfd, buf, MIN(rlen, CHAN_RBUF)); if (len <= 0) { if (errno != EINTR && errno != EAGAIN) { debug2("channel %d: ctl read<=0 rfd %d len %d", c->self, c->rfd, len); chan_read_failed(c); return 0; } } else buffer_append(&c->input, buf, len); } return buffer_len(&c->input); } static void channel_post_mux_client(Channel *c, fd_set *readset, fd_set *writeset) { u_int need; ssize_t len; if (!compat20) fatal("%s: entered with !compat20", __func__); if (c->rfd != -1 && !c->mux_pause && FD_ISSET(c->rfd, readset) && (c->istate == CHAN_INPUT_OPEN || c->istate == CHAN_INPUT_WAIT_DRAIN)) { /* * Don't not read past the precise end of packets to * avoid disrupting fd passing. */ if (read_mux(c, 4) < 4) /* read header */ return; need = get_u32(buffer_ptr(&c->input)); #define CHANNEL_MUX_MAX_PACKET (256 * 1024) if (need > CHANNEL_MUX_MAX_PACKET) { debug2("channel %d: packet too big %u > %u", c->self, CHANNEL_MUX_MAX_PACKET, need); chan_rcvd_oclose(c); return; } if (read_mux(c, need + 4) < need + 4) /* read body */ return; if (c->mux_rcb(c) != 0) { debug("channel %d: mux_rcb failed", c->self); chan_mark_dead(c); return; } } if (c->wfd != -1 && FD_ISSET(c->wfd, writeset) && buffer_len(&c->output) > 0) { len = write(c->wfd, buffer_ptr(&c->output), buffer_len(&c->output)); if (len < 0 && (errno == EINTR || errno == EAGAIN)) return; if (len <= 0) { chan_mark_dead(c); return; } buffer_consume(&c->output, len); } } static void channel_post_mux_listener(Channel *c, fd_set *readset, fd_set *writeset) { Channel *nc; struct sockaddr_storage addr; socklen_t addrlen; int newsock; uid_t euid; gid_t egid; if (!FD_ISSET(c->sock, readset)) return; debug("multiplexing control connection"); /* * Accept connection on control socket */ memset(&addr, 0, sizeof(addr)); addrlen = sizeof(addr); if ((newsock = accept(c->sock, (struct sockaddr*)&addr, &addrlen)) == -1) { error("%s accept: %s", __func__, strerror(errno)); return; } if (getpeereid(newsock, &euid, &egid) < 0) { error("%s getpeereid failed: %s", __func__, strerror(errno)); close(newsock); return; } if ((euid != 0) && (getuid() != euid)) { error("multiplex uid mismatch: peer euid %u != uid %u", (u_int)euid, (u_int)getuid()); close(newsock); return; } nc = channel_new("multiplex client", SSH_CHANNEL_MUX_CLIENT, newsock, newsock, -1, c->local_window_max, c->local_maxpacket, 0, "mux-control", 1); nc->mux_rcb = c->mux_rcb; debug3("%s: new mux channel %d fd %d", __func__, nc->self, nc->sock); /* establish state */ nc->mux_rcb(nc); /* mux state transitions must not elicit protocol messages */ nc->flags |= CHAN_LOCAL; } /* ARGSUSED */ static void channel_post_output_drain_13(Channel *c, fd_set *readset, fd_set *writeset) { int len; /* Send buffered output data to the socket. */ if (FD_ISSET(c->sock, writeset) && buffer_len(&c->output) > 0) { len = write(c->sock, buffer_ptr(&c->output), buffer_len(&c->output)); if (len <= 0) buffer_clear(&c->output); else buffer_consume(&c->output, len); } } static void channel_handler_init_20(void) { channel_pre[SSH_CHANNEL_OPEN] = &channel_pre_open; channel_pre[SSH_CHANNEL_X11_OPEN] = &channel_pre_x11_open; channel_pre[SSH_CHANNEL_PORT_LISTENER] = &channel_pre_listener; channel_pre[SSH_CHANNEL_RPORT_LISTENER] = &channel_pre_listener; channel_pre[SSH_CHANNEL_X11_LISTENER] = &channel_pre_listener; channel_pre[SSH_CHANNEL_AUTH_SOCKET] = &channel_pre_listener; channel_pre[SSH_CHANNEL_CONNECTING] = &channel_pre_connecting; channel_pre[SSH_CHANNEL_DYNAMIC] = &channel_pre_dynamic; channel_pre[SSH_CHANNEL_MUX_LISTENER] = &channel_pre_listener; channel_pre[SSH_CHANNEL_MUX_CLIENT] = &channel_pre_mux_client; channel_post[SSH_CHANNEL_OPEN] = &channel_post_open; channel_post[SSH_CHANNEL_PORT_LISTENER] = &channel_post_port_listener; channel_post[SSH_CHANNEL_RPORT_LISTENER] = &channel_post_port_listener; channel_post[SSH_CHANNEL_X11_LISTENER] = &channel_post_x11_listener; channel_post[SSH_CHANNEL_AUTH_SOCKET] = &channel_post_auth_listener; channel_post[SSH_CHANNEL_CONNECTING] = &channel_post_connecting; channel_post[SSH_CHANNEL_DYNAMIC] = &channel_post_open; channel_post[SSH_CHANNEL_MUX_LISTENER] = &channel_post_mux_listener; channel_post[SSH_CHANNEL_MUX_CLIENT] = &channel_post_mux_client; } static void channel_handler_init_13(void) { channel_pre[SSH_CHANNEL_OPEN] = &channel_pre_open_13; channel_pre[SSH_CHANNEL_X11_OPEN] = &channel_pre_x11_open_13; channel_pre[SSH_CHANNEL_X11_LISTENER] = &channel_pre_listener; channel_pre[SSH_CHANNEL_PORT_LISTENER] = &channel_pre_listener; channel_pre[SSH_CHANNEL_AUTH_SOCKET] = &channel_pre_listener; channel_pre[SSH_CHANNEL_INPUT_DRAINING] = &channel_pre_input_draining; channel_pre[SSH_CHANNEL_OUTPUT_DRAINING] = &channel_pre_output_draining; channel_pre[SSH_CHANNEL_CONNECTING] = &channel_pre_connecting; channel_pre[SSH_CHANNEL_DYNAMIC] = &channel_pre_dynamic; channel_post[SSH_CHANNEL_OPEN] = &channel_post_open; channel_post[SSH_CHANNEL_X11_LISTENER] = &channel_post_x11_listener; channel_post[SSH_CHANNEL_PORT_LISTENER] = &channel_post_port_listener; channel_post[SSH_CHANNEL_AUTH_SOCKET] = &channel_post_auth_listener; channel_post[SSH_CHANNEL_OUTPUT_DRAINING] = &channel_post_output_drain_13; channel_post[SSH_CHANNEL_CONNECTING] = &channel_post_connecting; channel_post[SSH_CHANNEL_DYNAMIC] = &channel_post_open; } static void channel_handler_init_15(void) { channel_pre[SSH_CHANNEL_OPEN] = &channel_pre_open; channel_pre[SSH_CHANNEL_X11_OPEN] = &channel_pre_x11_open; channel_pre[SSH_CHANNEL_X11_LISTENER] = &channel_pre_listener; channel_pre[SSH_CHANNEL_PORT_LISTENER] = &channel_pre_listener; channel_pre[SSH_CHANNEL_AUTH_SOCKET] = &channel_pre_listener; channel_pre[SSH_CHANNEL_CONNECTING] = &channel_pre_connecting; channel_pre[SSH_CHANNEL_DYNAMIC] = &channel_pre_dynamic; channel_post[SSH_CHANNEL_X11_LISTENER] = &channel_post_x11_listener; channel_post[SSH_CHANNEL_PORT_LISTENER] = &channel_post_port_listener; channel_post[SSH_CHANNEL_AUTH_SOCKET] = &channel_post_auth_listener; channel_post[SSH_CHANNEL_OPEN] = &channel_post_open; channel_post[SSH_CHANNEL_CONNECTING] = &channel_post_connecting; channel_post[SSH_CHANNEL_DYNAMIC] = &channel_post_open; } static void channel_handler_init(void) { int i; for (i = 0; i < SSH_CHANNEL_MAX_TYPE; i++) { channel_pre[i] = NULL; channel_post[i] = NULL; } if (compat20) channel_handler_init_20(); else if (compat13) channel_handler_init_13(); else channel_handler_init_15(); } /* gc dead channels */ static void channel_garbage_collect(Channel *c) { if (c == NULL) return; if (c->detach_user != NULL) { if (!chan_is_dead(c, c->detach_close)) return; debug2("channel %d: gc: notify user", c->self); c->detach_user(c->self, NULL); /* if we still have a callback */ if (c->detach_user != NULL) return; debug2("channel %d: gc: user detached", c->self); } if (!chan_is_dead(c, 1)) return; debug2("channel %d: garbage collecting", c->self); channel_free(c); } static void channel_handler(chan_fn *ftab[], fd_set *readset, fd_set *writeset) { static int did_init = 0; u_int i, oalloc; Channel *c; if (!did_init) { channel_handler_init(); did_init = 1; } for (i = 0, oalloc = channels_alloc; i < oalloc; i++) { c = channels[i]; if (c == NULL) continue; if (c->delayed) { if (ftab == channel_pre) c->delayed = 0; else continue; } if (ftab[c->type] != NULL) (*ftab[c->type])(c, readset, writeset); channel_garbage_collect(c); } } /* * Allocate/update select bitmasks and add any bits relevant to channels in * select bitmasks. */ void channel_prepare_select(fd_set **readsetp, fd_set **writesetp, int *maxfdp, u_int *nallocp, int rekeying) { u_int n, sz, nfdset; n = MAX(*maxfdp, channel_max_fd); nfdset = howmany(n+1, NFDBITS); /* Explicitly test here, because xrealloc isn't always called */ if (nfdset && SIZE_T_MAX / nfdset < sizeof(fd_mask)) fatal("channel_prepare_select: max_fd (%d) is too large", n); sz = nfdset * sizeof(fd_mask); /* perhaps check sz < nalloc/2 and shrink? */ if (*readsetp == NULL || sz > *nallocp) { *readsetp = xrealloc(*readsetp, nfdset, sizeof(fd_mask)); *writesetp = xrealloc(*writesetp, nfdset, sizeof(fd_mask)); *nallocp = sz; } *maxfdp = n; memset(*readsetp, 0, sz); memset(*writesetp, 0, sz); if (!rekeying) channel_handler(channel_pre, *readsetp, *writesetp); } /* * After select, perform any appropriate operations for channels which have * events pending. */ void channel_after_select(fd_set *readset, fd_set *writeset) { channel_handler(channel_post, readset, writeset); } /* If there is data to send to the connection, enqueue some of it now. */ void channel_output_poll(void) { Channel *c; u_int i, len; for (i = 0; i < channels_alloc; i++) { c = channels[i]; if (c == NULL) continue; /* * We are only interested in channels that can have buffered * incoming data. */ if (compat13) { if (c->type != SSH_CHANNEL_OPEN && c->type != SSH_CHANNEL_INPUT_DRAINING) continue; } else { if (c->type != SSH_CHANNEL_OPEN) continue; } if (compat20 && (c->flags & (CHAN_CLOSE_SENT|CHAN_CLOSE_RCVD))) { /* XXX is this true? */ debug3("channel %d: will not send data after close", c->self); continue; } /* Get the amount of buffered data for this channel. */ if ((c->istate == CHAN_INPUT_OPEN || c->istate == CHAN_INPUT_WAIT_DRAIN) && (len = buffer_len(&c->input)) > 0) { if (c->datagram) { if (len > 0) { u_char *data; u_int dlen; data = buffer_get_string(&c->input, &dlen); if (dlen > c->remote_window || dlen > c->remote_maxpacket) { debug("channel %d: datagram " "too big for channel", c->self); xfree(data); continue; } packet_start(SSH2_MSG_CHANNEL_DATA); packet_put_int(c->remote_id); packet_put_string(data, dlen); packet_send(); c->remote_window -= dlen + 4; xfree(data); } continue; } /* * Send some data for the other side over the secure * connection. */ if (compat20) { if (len > c->remote_window) len = c->remote_window; if (len > c->remote_maxpacket) len = c->remote_maxpacket; } else { if (packet_is_interactive()) { if (len > 1024) len = 512; } else { /* Keep the packets at reasonable size. */ if (len > packet_get_maxsize()/2) len = packet_get_maxsize()/2; } } if (len > 0) { packet_start(compat20 ? SSH2_MSG_CHANNEL_DATA : SSH_MSG_CHANNEL_DATA); packet_put_int(c->remote_id); packet_put_string(buffer_ptr(&c->input), len); packet_send(); buffer_consume(&c->input, len); c->remote_window -= len; } } else if (c->istate == CHAN_INPUT_WAIT_DRAIN) { if (compat13) fatal("cannot happen: istate == INPUT_WAIT_DRAIN for proto 1.3"); /* * input-buffer is empty and read-socket shutdown: * tell peer, that we will not send more data: send IEOF. * hack for extended data: delay EOF if EFD still in use. */ if (CHANNEL_EFD_INPUT_ACTIVE(c)) debug2("channel %d: ibuf_empty delayed efd %d/(%d)", c->self, c->efd, buffer_len(&c->extended)); else chan_ibuf_empty(c); } /* Send extended data, i.e. stderr */ if (compat20 && !(c->flags & CHAN_EOF_SENT) && c->remote_window > 0 && (len = buffer_len(&c->extended)) > 0 && c->extended_usage == CHAN_EXTENDED_READ) { debug2("channel %d: rwin %u elen %u euse %d", c->self, c->remote_window, buffer_len(&c->extended), c->extended_usage); if (len > c->remote_window) len = c->remote_window; if (len > c->remote_maxpacket) len = c->remote_maxpacket; packet_start(SSH2_MSG_CHANNEL_EXTENDED_DATA); packet_put_int(c->remote_id); packet_put_int(SSH2_EXTENDED_DATA_STDERR); packet_put_string(buffer_ptr(&c->extended), len); packet_send(); buffer_consume(&c->extended, len); c->remote_window -= len; debug2("channel %d: sent ext data %d", c->self, len); } } } /* -- protocol input */ /* ARGSUSED */ void channel_input_data(int type, u_int32_t seq, void *ctxt) { int id; char *data; u_int data_len, win_len; Channel *c; /* Get the channel number and verify it. */ id = packet_get_int(); c = channel_lookup(id); if (c == NULL) packet_disconnect("Received data for nonexistent channel %d.", id); /* Ignore any data for non-open channels (might happen on close) */ if (c->type != SSH_CHANNEL_OPEN && c->type != SSH_CHANNEL_X11_OPEN) return; /* Get the data. */ data = packet_get_string_ptr(&data_len); win_len = data_len; if (c->datagram) win_len += 4; /* string length header */ /* * Ignore data for protocol > 1.3 if output end is no longer open. * For protocol 2 the sending side is reducing its window as it sends * data, so we must 'fake' consumption of the data in order to ensure * that window updates are sent back. Otherwise the connection might * deadlock. */ if (!compat13 && c->ostate != CHAN_OUTPUT_OPEN) { if (compat20) { c->local_window -= win_len; c->local_consumed += win_len; } return; } if (compat20) { if (win_len > c->local_maxpacket) { logit("channel %d: rcvd big packet %d, maxpack %d", c->self, win_len, c->local_maxpacket); } if (win_len > c->local_window) { logit("channel %d: rcvd too much data %d, win %d", c->self, win_len, c->local_window); return; } c->local_window -= win_len; } if (c->datagram) buffer_put_string(&c->output, data, data_len); else buffer_append(&c->output, data, data_len); packet_check_eom(); } /* ARGSUSED */ void channel_input_extended_data(int type, u_int32_t seq, void *ctxt) { int id; char *data; u_int data_len, tcode; Channel *c; /* Get the channel number and verify it. */ id = packet_get_int(); c = channel_lookup(id); if (c == NULL) packet_disconnect("Received extended_data for bad channel %d.", id); if (c->type != SSH_CHANNEL_OPEN) { logit("channel %d: ext data for non open", id); return; } if (c->flags & CHAN_EOF_RCVD) { if (datafellows & SSH_BUG_EXTEOF) debug("channel %d: accepting ext data after eof", id); else packet_disconnect("Received extended_data after EOF " "on channel %d.", id); } tcode = packet_get_int(); if (c->efd == -1 || c->extended_usage != CHAN_EXTENDED_WRITE || tcode != SSH2_EXTENDED_DATA_STDERR) { logit("channel %d: bad ext data", c->self); return; } data = packet_get_string(&data_len); packet_check_eom(); if (data_len > c->local_window) { logit("channel %d: rcvd too much extended_data %d, win %d", c->self, data_len, c->local_window); xfree(data); return; } debug2("channel %d: rcvd ext data %d", c->self, data_len); c->local_window -= data_len; buffer_append(&c->extended, data, data_len); xfree(data); } /* ARGSUSED */ void channel_input_ieof(int type, u_int32_t seq, void *ctxt) { int id; Channel *c; id = packet_get_int(); packet_check_eom(); c = channel_lookup(id); if (c == NULL) packet_disconnect("Received ieof for nonexistent channel %d.", id); chan_rcvd_ieof(c); /* XXX force input close */ if (c->force_drain && c->istate == CHAN_INPUT_OPEN) { debug("channel %d: FORCE input drain", c->self); c->istate = CHAN_INPUT_WAIT_DRAIN; if (buffer_len(&c->input) == 0) chan_ibuf_empty(c); } } /* ARGSUSED */ void channel_input_close(int type, u_int32_t seq, void *ctxt) { int id; Channel *c; id = packet_get_int(); packet_check_eom(); c = channel_lookup(id); if (c == NULL) packet_disconnect("Received close for nonexistent channel %d.", id); /* * Send a confirmation that we have closed the channel and no more * data is coming for it. */ packet_start(SSH_MSG_CHANNEL_CLOSE_CONFIRMATION); packet_put_int(c->remote_id); packet_send(); /* * If the channel is in closed state, we have sent a close request, * and the other side will eventually respond with a confirmation. * Thus, we cannot free the channel here, because then there would be * no-one to receive the confirmation. The channel gets freed when * the confirmation arrives. */ if (c->type != SSH_CHANNEL_CLOSED) { /* * Not a closed channel - mark it as draining, which will * cause it to be freed later. */ buffer_clear(&c->input); c->type = SSH_CHANNEL_OUTPUT_DRAINING; } } /* proto version 1.5 overloads CLOSE_CONFIRMATION with OCLOSE */ /* ARGSUSED */ void channel_input_oclose(int type, u_int32_t seq, void *ctxt) { int id = packet_get_int(); Channel *c = channel_lookup(id); packet_check_eom(); if (c == NULL) packet_disconnect("Received oclose for nonexistent channel %d.", id); chan_rcvd_oclose(c); } /* ARGSUSED */ void channel_input_close_confirmation(int type, u_int32_t seq, void *ctxt) { int id = packet_get_int(); Channel *c = channel_lookup(id); packet_check_eom(); if (c == NULL) packet_disconnect("Received close confirmation for " "out-of-range channel %d.", id); if (c->type != SSH_CHANNEL_CLOSED) packet_disconnect("Received close confirmation for " "non-closed channel %d (type %d).", id, c->type); channel_free(c); } /* ARGSUSED */ void channel_input_open_confirmation(int type, u_int32_t seq, void *ctxt) { int id, remote_id; Channel *c; id = packet_get_int(); c = channel_lookup(id); if (c==NULL || c->type != SSH_CHANNEL_OPENING) packet_disconnect("Received open confirmation for " "non-opening channel %d.", id); remote_id = packet_get_int(); /* Record the remote channel number and mark that the channel is now open. */ c->remote_id = remote_id; c->type = SSH_CHANNEL_OPEN; if (compat20) { c->remote_window = packet_get_int(); c->remote_maxpacket = packet_get_int(); if (c->open_confirm) { debug2("callback start"); c->open_confirm(c->self, 1, c->open_confirm_ctx); debug2("callback done"); } debug2("channel %d: open confirm rwindow %u rmax %u", c->self, c->remote_window, c->remote_maxpacket); } packet_check_eom(); } static char * reason2txt(int reason) { switch (reason) { case SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED: return "administratively prohibited"; case SSH2_OPEN_CONNECT_FAILED: return "connect failed"; case SSH2_OPEN_UNKNOWN_CHANNEL_TYPE: return "unknown channel type"; case SSH2_OPEN_RESOURCE_SHORTAGE: return "resource shortage"; } return "unknown reason"; } /* ARGSUSED */ void channel_input_open_failure(int type, u_int32_t seq, void *ctxt) { int id, reason; char *msg = NULL, *lang = NULL; Channel *c; id = packet_get_int(); c = channel_lookup(id); if (c==NULL || c->type != SSH_CHANNEL_OPENING) packet_disconnect("Received open failure for " "non-opening channel %d.", id); if (compat20) { reason = packet_get_int(); if (!(datafellows & SSH_BUG_OPENFAILURE)) { msg = packet_get_string(NULL); lang = packet_get_string(NULL); } logit("channel %d: open failed: %s%s%s", id, reason2txt(reason), msg ? ": ": "", msg ? msg : ""); if (msg != NULL) xfree(msg); if (lang != NULL) xfree(lang); if (c->open_confirm) { debug2("callback start"); c->open_confirm(c->self, 0, c->open_confirm_ctx); debug2("callback done"); } } packet_check_eom(); /* Schedule the channel for cleanup/deletion. */ chan_mark_dead(c); } /* ARGSUSED */ void channel_input_window_adjust(int type, u_int32_t seq, void *ctxt) { Channel *c; int id; u_int adjust; if (!compat20) return; /* Get the channel number and verify it. */ id = packet_get_int(); c = channel_lookup(id); if (c == NULL) { logit("Received window adjust for non-open channel %d.", id); return; } adjust = packet_get_int(); packet_check_eom(); debug2("channel %d: rcvd adjust %u", id, adjust); c->remote_window += adjust; } /* ARGSUSED */ void channel_input_port_open(int type, u_int32_t seq, void *ctxt) { Channel *c = NULL; u_short host_port; char *host, *originator_string; int remote_id; remote_id = packet_get_int(); host = packet_get_string(NULL); host_port = packet_get_int(); if (packet_get_protocol_flags() & SSH_PROTOFLAG_HOST_IN_FWD_OPEN) { originator_string = packet_get_string(NULL); } else { originator_string = xstrdup("unknown (remote did not supply name)"); } packet_check_eom(); c = channel_connect_to(host, host_port, "connected socket", originator_string); xfree(originator_string); xfree(host); if (c == NULL) { packet_start(SSH_MSG_CHANNEL_OPEN_FAILURE); packet_put_int(remote_id); packet_send(); } else c->remote_id = remote_id; } /* ARGSUSED */ void channel_input_status_confirm(int type, u_int32_t seq, void *ctxt) { Channel *c; struct channel_confirm *cc; int id; /* Reset keepalive timeout */ packet_set_alive_timeouts(0); id = packet_get_int(); packet_check_eom(); debug2("channel_input_status_confirm: type %d id %d", type, id); if ((c = channel_lookup(id)) == NULL) { logit("channel_input_status_confirm: %d: unknown", id); return; } ; if ((cc = TAILQ_FIRST(&c->status_confirms)) == NULL) return; cc->cb(type, c, cc->ctx); TAILQ_REMOVE(&c->status_confirms, cc, entry); bzero(cc, sizeof(*cc)); xfree(cc); } /* -- tcp forwarding */ void channel_set_af(int af) { IPv4or6 = af; } +void +channel_set_hpn(int disabled, u_int buf_size) +{ + hpn_disabled = disabled; + buffer_size = buf_size; + debug("HPN Disabled: %d, HPN Buffer Size: %d", + hpn_disabled, buffer_size); +} + static int channel_setup_fwd_listener(int type, const char *listen_addr, u_short listen_port, int *allocated_listen_port, const char *host_to_connect, u_short port_to_connect, int gateway_ports) { Channel *c; int sock, r, success = 0, wildcard = 0, is_client; struct addrinfo hints, *ai, *aitop; const char *host, *addr; char ntop[NI_MAXHOST], strport[NI_MAXSERV]; in_port_t *lport_p; host = (type == SSH_CHANNEL_RPORT_LISTENER) ? listen_addr : host_to_connect; is_client = (type == SSH_CHANNEL_PORT_LISTENER); if (host == NULL) { error("No forward host name."); return 0; } if (strlen(host) >= NI_MAXHOST) { error("Forward host name too long."); return 0; } /* * Determine whether or not a port forward listens to loopback, * specified address or wildcard. On the client, a specified bind * address will always override gateway_ports. On the server, a * gateway_ports of 1 (``yes'') will override the client's * specification and force a wildcard bind, whereas a value of 2 * (``clientspecified'') will bind to whatever address the client * asked for. * * Special-case listen_addrs are: * * "0.0.0.0" -> wildcard v4/v6 if SSH_OLD_FORWARD_ADDR * "" (empty string), "*" -> wildcard v4/v6 * "localhost" -> loopback v4/v6 */ addr = NULL; if (listen_addr == NULL) { /* No address specified: default to gateway_ports setting */ if (gateway_ports) wildcard = 1; } else if (gateway_ports || is_client) { if (((datafellows & SSH_OLD_FORWARD_ADDR) && strcmp(listen_addr, "0.0.0.0") == 0 && is_client == 0) || *listen_addr == '\0' || strcmp(listen_addr, "*") == 0 || (!is_client && gateway_ports == 1)) wildcard = 1; else if (strcmp(listen_addr, "localhost") != 0) addr = listen_addr; } debug3("channel_setup_fwd_listener: type %d wildcard %d addr %s", type, wildcard, (addr == NULL) ? "NULL" : addr); /* * getaddrinfo returns a loopback address if the hostname is * set to NULL and hints.ai_flags is not AI_PASSIVE */ memset(&hints, 0, sizeof(hints)); hints.ai_family = IPv4or6; hints.ai_flags = wildcard ? AI_PASSIVE : 0; hints.ai_socktype = SOCK_STREAM; snprintf(strport, sizeof strport, "%d", listen_port); if ((r = getaddrinfo(addr, strport, &hints, &aitop)) != 0) { if (addr == NULL) { /* This really shouldn't happen */ packet_disconnect("getaddrinfo: fatal error: %s", ssh_gai_strerror(r)); } else { error("channel_setup_fwd_listener: " "getaddrinfo(%.64s): %s", addr, ssh_gai_strerror(r)); } return 0; } if (allocated_listen_port != NULL) *allocated_listen_port = 0; for (ai = aitop; ai; ai = ai->ai_next) { switch (ai->ai_family) { case AF_INET: lport_p = &((struct sockaddr_in *)ai->ai_addr)-> sin_port; break; case AF_INET6: lport_p = &((struct sockaddr_in6 *)ai->ai_addr)-> sin6_port; break; default: continue; } /* * If allocating a port for -R forwards, then use the * same port for all address families. */ if (type == SSH_CHANNEL_RPORT_LISTENER && listen_port == 0 && allocated_listen_port != NULL && *allocated_listen_port > 0) *lport_p = htons(*allocated_listen_port); if (getnameinfo(ai->ai_addr, ai->ai_addrlen, ntop, sizeof(ntop), strport, sizeof(strport), NI_NUMERICHOST|NI_NUMERICSERV) != 0) { error("channel_setup_fwd_listener: getnameinfo failed"); continue; } /* Create a port to listen for the host. */ sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); if (sock < 0) { /* this is no error since kernel may not support ipv6 */ verbose("socket: %.100s", strerror(errno)); continue; } channel_set_reuseaddr(sock); if (ai->ai_family == AF_INET6) sock_set_v6only(sock); debug("Local forwarding listening on %s port %s.", ntop, strport); /* Bind the socket to the address. */ if (bind(sock, ai->ai_addr, ai->ai_addrlen) < 0) { /* address can be in use ipv6 address is already bound */ if (!ai->ai_next) error("bind: %.100s", strerror(errno)); else verbose("bind: %.100s", strerror(errno)); close(sock); continue; } /* Start listening for connections on the socket. */ if (listen(sock, SSH_LISTEN_BACKLOG) < 0) { error("listen: %.100s", strerror(errno)); close(sock); continue; } /* * listen_port == 0 requests a dynamically allocated port - * record what we got. */ if (type == SSH_CHANNEL_RPORT_LISTENER && listen_port == 0 && allocated_listen_port != NULL && *allocated_listen_port == 0) { *allocated_listen_port = get_sock_port(sock, 1); debug("Allocated listen port %d", *allocated_listen_port); } - /* Allocate a channel number for the socket. */ - c = channel_new("port listener", type, sock, sock, -1, - CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, - 0, "port listener", 1); + /* + * Allocate a channel number for the socket. Explicitly test + * for hpn disabled option. If true use smaller window size. + */ + if (hpn_disabled) + c = channel_new("port listener", type, sock, sock, -1, + CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, + 0, "port listener", 1); + else + c = channel_new("port listener", type, sock, sock, -1, + buffer_size, CHAN_TCP_PACKET_DEFAULT, + 0, "port listener", 1); c->path = xstrdup(host); c->host_port = port_to_connect; c->listening_port = listen_port; success = 1; } if (success == 0) error("channel_setup_fwd_listener: cannot listen to port: %d", listen_port); freeaddrinfo(aitop); return success; } int channel_cancel_rport_listener(const char *host, u_short port) { u_int i; int found = 0; for (i = 0; i < channels_alloc; i++) { Channel *c = channels[i]; if (c != NULL && c->type == SSH_CHANNEL_RPORT_LISTENER && strcmp(c->path, host) == 0 && c->listening_port == port) { debug2("%s: close channel %d", __func__, i); channel_free(c); found = 1; } } return (found); } /* protocol local port fwd, used by ssh (and sshd in v1) */ int channel_setup_local_fwd_listener(const char *listen_host, u_short listen_port, const char *host_to_connect, u_short port_to_connect, int gateway_ports) { return channel_setup_fwd_listener(SSH_CHANNEL_PORT_LISTENER, listen_host, listen_port, NULL, host_to_connect, port_to_connect, gateway_ports); } /* protocol v2 remote port fwd, used by sshd */ int channel_setup_remote_fwd_listener(const char *listen_address, u_short listen_port, int *allocated_listen_port, int gateway_ports) { return channel_setup_fwd_listener(SSH_CHANNEL_RPORT_LISTENER, listen_address, listen_port, allocated_listen_port, NULL, 0, gateway_ports); } /* * Initiate forwarding of connections to port "port" on remote host through * the secure channel to host:port from local side. */ int channel_request_remote_forwarding(const char *listen_host, u_short listen_port, const char *host_to_connect, u_short port_to_connect) { int type, success = 0; /* Send the forward request to the remote side. */ if (compat20) { const char *address_to_bind; if (listen_host == NULL) { if (datafellows & SSH_BUG_RFWD_ADDR) address_to_bind = "127.0.0.1"; else address_to_bind = "localhost"; } else if (*listen_host == '\0' || strcmp(listen_host, "*") == 0) { if (datafellows & SSH_BUG_RFWD_ADDR) address_to_bind = "0.0.0.0"; else address_to_bind = ""; } else address_to_bind = listen_host; packet_start(SSH2_MSG_GLOBAL_REQUEST); packet_put_cstring("tcpip-forward"); packet_put_char(1); /* boolean: want reply */ packet_put_cstring(address_to_bind); packet_put_int(listen_port); packet_send(); packet_write_wait(); /* Assume that server accepts the request */ success = 1; } else { packet_start(SSH_CMSG_PORT_FORWARD_REQUEST); packet_put_int(listen_port); packet_put_cstring(host_to_connect); packet_put_int(port_to_connect); packet_send(); packet_write_wait(); /* Wait for response from the remote side. */ type = packet_read(); switch (type) { case SSH_SMSG_SUCCESS: success = 1; break; case SSH_SMSG_FAILURE: break; default: /* Unknown packet */ packet_disconnect("Protocol error for port forward request:" "received packet type %d.", type); } } if (success) { /* Record that connection to this host/port is permitted. */ permitted_opens = xrealloc(permitted_opens, num_permitted_opens + 1, sizeof(*permitted_opens)); permitted_opens[num_permitted_opens].host_to_connect = xstrdup(host_to_connect); permitted_opens[num_permitted_opens].port_to_connect = port_to_connect; permitted_opens[num_permitted_opens].listen_port = listen_port; num_permitted_opens++; } return (success ? 0 : -1); } /* * Request cancellation of remote forwarding of connection host:port from * local side. */ void channel_request_rforward_cancel(const char *host, u_short port) { int i; if (!compat20) return; for (i = 0; i < num_permitted_opens; i++) { if (permitted_opens[i].host_to_connect != NULL && permitted_opens[i].listen_port == port) break; } if (i >= num_permitted_opens) { debug("%s: requested forward not found", __func__); return; } packet_start(SSH2_MSG_GLOBAL_REQUEST); packet_put_cstring("cancel-tcpip-forward"); packet_put_char(0); packet_put_cstring(host == NULL ? "" : host); packet_put_int(port); packet_send(); permitted_opens[i].listen_port = 0; permitted_opens[i].port_to_connect = 0; xfree(permitted_opens[i].host_to_connect); permitted_opens[i].host_to_connect = NULL; } /* * This is called after receiving CHANNEL_FORWARDING_REQUEST. This initates * listening for the port, and sends back a success reply (or disconnect * message if there was an error). */ int channel_input_port_forward_request(int is_root, int gateway_ports) { u_short port, host_port; int success = 0; char *hostname; /* Get arguments from the packet. */ port = packet_get_int(); hostname = packet_get_string(NULL); host_port = packet_get_int(); #ifndef HAVE_CYGWIN /* * Check that an unprivileged user is not trying to forward a * privileged port. */ if (port < IPPORT_RESERVED && !is_root) packet_disconnect( "Requested forwarding of port %d but user is not root.", port); if (host_port == 0) packet_disconnect("Dynamic forwarding denied."); #endif /* Initiate forwarding */ success = channel_setup_local_fwd_listener(NULL, port, hostname, host_port, gateway_ports); /* Free the argument string. */ xfree(hostname); return (success ? 0 : -1); } /* * Permits opening to any host/port if permitted_opens[] is empty. This is * usually called by the server, because the user could connect to any port * anyway, and the server has no way to know but to trust the client anyway. */ void channel_permit_all_opens(void) { if (num_permitted_opens == 0) all_opens_permitted = 1; } void channel_add_permitted_opens(char *host, int port) { debug("allow port forwarding to host %s port %d", host, port); permitted_opens = xrealloc(permitted_opens, num_permitted_opens + 1, sizeof(*permitted_opens)); permitted_opens[num_permitted_opens].host_to_connect = xstrdup(host); permitted_opens[num_permitted_opens].port_to_connect = port; num_permitted_opens++; all_opens_permitted = 0; } int channel_add_adm_permitted_opens(char *host, int port) { debug("config allows port forwarding to host %s port %d", host, port); permitted_adm_opens = xrealloc(permitted_adm_opens, num_adm_permitted_opens + 1, sizeof(*permitted_adm_opens)); permitted_adm_opens[num_adm_permitted_opens].host_to_connect = xstrdup(host); permitted_adm_opens[num_adm_permitted_opens].port_to_connect = port; return ++num_adm_permitted_opens; } void channel_clear_permitted_opens(void) { int i; for (i = 0; i < num_permitted_opens; i++) if (permitted_opens[i].host_to_connect != NULL) xfree(permitted_opens[i].host_to_connect); if (num_permitted_opens > 0) { xfree(permitted_opens); permitted_opens = NULL; } num_permitted_opens = 0; } void channel_clear_adm_permitted_opens(void) { int i; for (i = 0; i < num_adm_permitted_opens; i++) if (permitted_adm_opens[i].host_to_connect != NULL) xfree(permitted_adm_opens[i].host_to_connect); if (num_adm_permitted_opens > 0) { xfree(permitted_adm_opens); permitted_adm_opens = NULL; } num_adm_permitted_opens = 0; } void channel_print_adm_permitted_opens(void) { int i; printf("permitopen"); if (num_adm_permitted_opens == 0) { printf(" any\n"); return; } for (i = 0; i < num_adm_permitted_opens; i++) if (permitted_adm_opens[i].host_to_connect != NULL) printf(" %s:%d", permitted_adm_opens[i].host_to_connect, permitted_adm_opens[i].port_to_connect); printf("\n"); } /* Try to start non-blocking connect to next host in cctx list */ static int connect_next(struct channel_connect *cctx) { int sock, saved_errno; char ntop[NI_MAXHOST], strport[NI_MAXSERV]; for (; cctx->ai; cctx->ai = cctx->ai->ai_next) { if (cctx->ai->ai_family != AF_INET && cctx->ai->ai_family != AF_INET6) continue; if (getnameinfo(cctx->ai->ai_addr, cctx->ai->ai_addrlen, ntop, sizeof(ntop), strport, sizeof(strport), NI_NUMERICHOST|NI_NUMERICSERV) != 0) { error("connect_next: getnameinfo failed"); continue; } if ((sock = socket(cctx->ai->ai_family, cctx->ai->ai_socktype, cctx->ai->ai_protocol)) == -1) { if (cctx->ai->ai_next == NULL) error("socket: %.100s", strerror(errno)); else verbose("socket: %.100s", strerror(errno)); continue; } if (set_nonblock(sock) == -1) fatal("%s: set_nonblock(%d)", __func__, sock); if (connect(sock, cctx->ai->ai_addr, cctx->ai->ai_addrlen) == -1 && errno != EINPROGRESS) { debug("connect_next: host %.100s ([%.100s]:%s): " "%.100s", cctx->host, ntop, strport, strerror(errno)); saved_errno = errno; close(sock); errno = saved_errno; continue; /* fail -- try next */ } debug("connect_next: host %.100s ([%.100s]:%s) " "in progress, fd=%d", cctx->host, ntop, strport, sock); cctx->ai = cctx->ai->ai_next; set_nodelay(sock); return sock; } return -1; } static void channel_connect_ctx_free(struct channel_connect *cctx) { xfree(cctx->host); if (cctx->aitop) freeaddrinfo(cctx->aitop); bzero(cctx, sizeof(*cctx)); cctx->host = NULL; cctx->ai = cctx->aitop = NULL; } /* Return CONNECTING channel to remote host, port */ static Channel * connect_to(const char *host, u_short port, char *ctype, char *rname) { struct addrinfo hints; int gaierr; int sock = -1; char strport[NI_MAXSERV]; struct channel_connect cctx; Channel *c; memset(&cctx, 0, sizeof(cctx)); memset(&hints, 0, sizeof(hints)); hints.ai_family = IPv4or6; hints.ai_socktype = SOCK_STREAM; snprintf(strport, sizeof strport, "%d", port); if ((gaierr = getaddrinfo(host, strport, &hints, &cctx.aitop)) != 0) { error("connect_to %.100s: unknown host (%s)", host, ssh_gai_strerror(gaierr)); return NULL; } cctx.host = xstrdup(host); cctx.port = port; cctx.ai = cctx.aitop; if ((sock = connect_next(&cctx)) == -1) { error("connect to %.100s port %d failed: %s", host, port, strerror(errno)); channel_connect_ctx_free(&cctx); return NULL; } c = channel_new(ctype, SSH_CHANNEL_CONNECTING, sock, sock, -1, CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, 0, rname, 1); c->connect_ctx = cctx; return c; } Channel * channel_connect_by_listen_address(u_short listen_port, char *ctype, char *rname) { int i; for (i = 0; i < num_permitted_opens; i++) { if (permitted_opens[i].host_to_connect != NULL && permitted_opens[i].listen_port == listen_port) { return connect_to( permitted_opens[i].host_to_connect, permitted_opens[i].port_to_connect, ctype, rname); } } error("WARNING: Server requests forwarding for unknown listen_port %d", listen_port); return NULL; } /* Check if connecting to that port is permitted and connect. */ Channel * channel_connect_to(const char *host, u_short port, char *ctype, char *rname) { int i, permit, permit_adm = 1; permit = all_opens_permitted; if (!permit) { for (i = 0; i < num_permitted_opens; i++) if (permitted_opens[i].host_to_connect != NULL && permitted_opens[i].port_to_connect == port && strcmp(permitted_opens[i].host_to_connect, host) == 0) permit = 1; } if (num_adm_permitted_opens > 0) { permit_adm = 0; for (i = 0; i < num_adm_permitted_opens; i++) if (permitted_adm_opens[i].host_to_connect != NULL && permitted_adm_opens[i].port_to_connect == port && strcmp(permitted_adm_opens[i].host_to_connect, host) == 0) permit_adm = 1; } if (!permit || !permit_adm) { logit("Received request to connect to host %.100s port %d, " "but the request was denied.", host, port); return NULL; } return connect_to(host, port, ctype, rname); } void channel_send_window_changes(void) { u_int i; struct winsize ws; for (i = 0; i < channels_alloc; i++) { if (channels[i] == NULL || !channels[i]->client_tty || channels[i]->type != SSH_CHANNEL_OPEN) continue; if (ioctl(channels[i]->rfd, TIOCGWINSZ, &ws) < 0) continue; channel_request_start(i, "window-change", 0); packet_put_int((u_int)ws.ws_col); packet_put_int((u_int)ws.ws_row); packet_put_int((u_int)ws.ws_xpixel); packet_put_int((u_int)ws.ws_ypixel); packet_send(); } } /* -- X11 forwarding */ /* * Creates an internet domain socket for listening for X11 connections. * Returns 0 and a suitable display number for the DISPLAY variable * stored in display_numberp , or -1 if an error occurs. */ int x11_create_display_inet(int x11_display_offset, int x11_use_localhost, int single_connection, u_int *display_numberp, int **chanids) { Channel *nc = NULL; int display_number, sock; u_short port; struct addrinfo hints, *ai, *aitop; char strport[NI_MAXSERV]; int gaierr, n, num_socks = 0, socks[NUM_SOCKS]; if (chanids == NULL) return -1; for (display_number = x11_display_offset; display_number < MAX_DISPLAYS; display_number++) { port = 6000 + display_number; memset(&hints, 0, sizeof(hints)); hints.ai_family = IPv4or6; hints.ai_flags = x11_use_localhost ? 0: AI_PASSIVE; hints.ai_socktype = SOCK_STREAM; snprintf(strport, sizeof strport, "%d", port); if ((gaierr = getaddrinfo(NULL, strport, &hints, &aitop)) != 0) { error("getaddrinfo: %.100s", ssh_gai_strerror(gaierr)); return -1; } for (ai = aitop; ai; ai = ai->ai_next) { if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) continue; sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); if (sock < 0) { if ((errno != EINVAL) && (errno != EAFNOSUPPORT) #ifdef EPFNOSUPPORT && (errno != EPFNOSUPPORT) #endif ) { error("socket: %.100s", strerror(errno)); freeaddrinfo(aitop); return -1; } else { debug("x11_create_display_inet: Socket family %d not supported", ai->ai_family); continue; } } if (ai->ai_family == AF_INET6) sock_set_v6only(sock); if (x11_use_localhost) channel_set_reuseaddr(sock); if (bind(sock, ai->ai_addr, ai->ai_addrlen) < 0) { debug2("bind port %d: %.100s", port, strerror(errno)); close(sock); for (n = 0; n < num_socks; n++) { close(socks[n]); } num_socks = 0; break; } socks[num_socks++] = sock; if (num_socks == NUM_SOCKS) break; } freeaddrinfo(aitop); if (num_socks > 0) break; } if (display_number >= MAX_DISPLAYS) { error("Failed to allocate internet-domain X11 display socket."); return -1; } /* Start listening for connections on the socket. */ for (n = 0; n < num_socks; n++) { sock = socks[n]; if (listen(sock, SSH_LISTEN_BACKLOG) < 0) { error("listen: %.100s", strerror(errno)); close(sock); return -1; } } /* Allocate a channel for each socket. */ *chanids = xcalloc(num_socks + 1, sizeof(**chanids)); for (n = 0; n < num_socks; n++) { sock = socks[n]; - nc = channel_new("x11 listener", - SSH_CHANNEL_X11_LISTENER, sock, sock, -1, - CHAN_X11_WINDOW_DEFAULT, CHAN_X11_PACKET_DEFAULT, - 0, "X11 inet listener", 1); + if (hpn_disabled) + nc = channel_new("x11 listener", + SSH_CHANNEL_X11_LISTENER, sock, sock, -1, + CHAN_X11_WINDOW_DEFAULT, CHAN_X11_PACKET_DEFAULT, + 0, "X11 inet listener", 1); + else + nc = channel_new("x11 listener", + SSH_CHANNEL_X11_LISTENER, sock, sock, -1, + buffer_size, CHAN_X11_PACKET_DEFAULT, + 0, "X11 inet listener", 1); nc->single_connection = single_connection; (*chanids)[n] = nc->self; } (*chanids)[n] = -1; /* Return the display number for the DISPLAY environment variable. */ *display_numberp = display_number; return (0); } static int connect_local_xsocket_path(const char *pathname) { int sock; struct sockaddr_un addr; sock = socket(AF_UNIX, SOCK_STREAM, 0); if (sock < 0) error("socket: %.100s", strerror(errno)); memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; strlcpy(addr.sun_path, pathname, sizeof addr.sun_path); if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) == 0) return sock; close(sock); error("connect %.100s: %.100s", addr.sun_path, strerror(errno)); return -1; } static int connect_local_xsocket(u_int dnr) { char buf[1024]; snprintf(buf, sizeof buf, _PATH_UNIX_X, dnr); return connect_local_xsocket_path(buf); } int x11_connect_display(void) { u_int display_number; const char *display; char buf[1024], *cp; struct addrinfo hints, *ai, *aitop; char strport[NI_MAXSERV]; int gaierr, sock = 0; /* Try to open a socket for the local X server. */ display = getenv("DISPLAY"); if (!display) { error("DISPLAY not set."); return -1; } /* * Now we decode the value of the DISPLAY variable and make a * connection to the real X server. */ /* Check if the display is from launchd. */ #ifdef __APPLE__ if (strncmp(display, "/tmp/launch", 11) == 0) { sock = connect_local_xsocket_path(display); if (sock < 0) return -1; /* OK, we now have a connection to the display. */ return sock; } #endif /* * Check if it is a unix domain socket. Unix domain displays are in * one of the following formats: unix:d[.s], :d[.s], ::d[.s] */ if (strncmp(display, "unix:", 5) == 0 || display[0] == ':') { /* Connect to the unix domain socket. */ if (sscanf(strrchr(display, ':') + 1, "%u", &display_number) != 1) { error("Could not parse display number from DISPLAY: %.100s", display); return -1; } /* Create a socket. */ sock = connect_local_xsocket(display_number); if (sock < 0) return -1; /* OK, we now have a connection to the display. */ return sock; } /* * Connect to an inet socket. The DISPLAY value is supposedly * hostname:d[.s], where hostname may also be numeric IP address. */ strlcpy(buf, display, sizeof(buf)); cp = strchr(buf, ':'); if (!cp) { error("Could not find ':' in DISPLAY: %.100s", display); return -1; } *cp = 0; /* buf now contains the host name. But first we parse the display number. */ if (sscanf(cp + 1, "%u", &display_number) != 1) { error("Could not parse display number from DISPLAY: %.100s", display); return -1; } /* Look up the host address */ memset(&hints, 0, sizeof(hints)); hints.ai_family = IPv4or6; hints.ai_socktype = SOCK_STREAM; snprintf(strport, sizeof strport, "%u", 6000 + display_number); if ((gaierr = getaddrinfo(buf, strport, &hints, &aitop)) != 0) { error("%.100s: unknown host. (%s)", buf, ssh_gai_strerror(gaierr)); return -1; } for (ai = aitop; ai; ai = ai->ai_next) { /* Create a socket. */ sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); if (sock < 0) { debug2("socket: %.100s", strerror(errno)); continue; } /* Connect it to the display. */ if (connect(sock, ai->ai_addr, ai->ai_addrlen) < 0) { debug2("connect %.100s port %u: %.100s", buf, 6000 + display_number, strerror(errno)); close(sock); continue; } /* Success */ break; } freeaddrinfo(aitop); if (!ai) { error("connect %.100s port %u: %.100s", buf, 6000 + display_number, strerror(errno)); return -1; } set_nodelay(sock); return sock; } /* * This is called when SSH_SMSG_X11_OPEN is received. The packet contains * the remote channel number. We should do whatever we want, and respond * with either SSH_MSG_OPEN_CONFIRMATION or SSH_MSG_OPEN_FAILURE. */ /* ARGSUSED */ void x11_input_open(int type, u_int32_t seq, void *ctxt) { Channel *c = NULL; int remote_id, sock = 0; char *remote_host; debug("Received X11 open request."); remote_id = packet_get_int(); if (packet_get_protocol_flags() & SSH_PROTOFLAG_HOST_IN_FWD_OPEN) { remote_host = packet_get_string(NULL); } else { remote_host = xstrdup("unknown (remote did not supply name)"); } packet_check_eom(); /* Obtain a connection to the real X display. */ sock = x11_connect_display(); if (sock != -1) { /* Allocate a channel for this connection. */ c = channel_new("connected x11 socket", SSH_CHANNEL_X11_OPEN, sock, sock, -1, 0, 0, 0, remote_host, 1); c->remote_id = remote_id; c->force_drain = 1; } xfree(remote_host); if (c == NULL) { /* Send refusal to the remote host. */ packet_start(SSH_MSG_CHANNEL_OPEN_FAILURE); packet_put_int(remote_id); } else { /* Send a confirmation to the remote host. */ packet_start(SSH_MSG_CHANNEL_OPEN_CONFIRMATION); packet_put_int(remote_id); packet_put_int(c->self); } packet_send(); } /* dummy protocol handler that denies SSH-1 requests (agent/x11) */ /* ARGSUSED */ void deny_input_open(int type, u_int32_t seq, void *ctxt) { int rchan = packet_get_int(); switch (type) { case SSH_SMSG_AGENT_OPEN: error("Warning: ssh server tried agent forwarding."); break; case SSH_SMSG_X11_OPEN: error("Warning: ssh server tried X11 forwarding."); break; default: error("deny_input_open: type %d", type); break; } error("Warning: this is probably a break-in attempt by a malicious server."); packet_start(SSH_MSG_CHANNEL_OPEN_FAILURE); packet_put_int(rchan); packet_send(); } /* * Requests forwarding of X11 connections, generates fake authentication * data, and enables authentication spoofing. * This should be called in the client only. */ void x11_request_forwarding_with_spoofing(int client_session_id, const char *disp, const char *proto, const char *data) { u_int data_len = (u_int) strlen(data) / 2; u_int i, value; char *new_data; int screen_number; const char *cp; u_int32_t rnd = 0; if (x11_saved_display == NULL) x11_saved_display = xstrdup(disp); else if (strcmp(disp, x11_saved_display) != 0) { error("x11_request_forwarding_with_spoofing: different " "$DISPLAY already forwarded"); return; } cp = strchr(disp, ':'); if (cp) cp = strchr(cp, '.'); if (cp) screen_number = (u_int)strtonum(cp + 1, 0, 400, NULL); else screen_number = 0; if (x11_saved_proto == NULL) { /* Save protocol name. */ x11_saved_proto = xstrdup(proto); /* * Extract real authentication data and generate fake data * of the same length. */ x11_saved_data = xmalloc(data_len); x11_fake_data = xmalloc(data_len); for (i = 0; i < data_len; i++) { if (sscanf(data + 2 * i, "%2x", &value) != 1) fatal("x11_request_forwarding: bad " "authentication data: %.100s", data); if (i % 4 == 0) rnd = arc4random(); x11_saved_data[i] = value; x11_fake_data[i] = rnd & 0xff; rnd >>= 8; } x11_saved_data_len = data_len; x11_fake_data_len = data_len; } /* Convert the fake data into hex. */ new_data = tohex(x11_fake_data, data_len); /* Send the request packet. */ if (compat20) { channel_request_start(client_session_id, "x11-req", 0); packet_put_char(0); /* XXX bool single connection */ } else { packet_start(SSH_CMSG_X11_REQUEST_FORWARDING); } packet_put_cstring(proto); packet_put_cstring(new_data); packet_put_int(screen_number); packet_send(); packet_write_wait(); xfree(new_data); } /* -- agent forwarding */ /* Sends a message to the server to request authentication fd forwarding. */ void auth_request_forwarding(void) { packet_start(SSH_CMSG_AGENT_REQUEST_FORWARDING); packet_send(); packet_write_wait(); } diff --git a/crypto/openssh/channels.h b/crypto/openssh/channels.h index 0680ed00e465..a967a7e1aa9e 100644 --- a/crypto/openssh/channels.h +++ b/crypto/openssh/channels.h @@ -1,297 +1,307 @@ /* $OpenBSD: channels.h,v 1.104 2010/05/14 23:29:23 djm Exp $ */ +/* $FreeBSD$ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved * * As far as I am concerned, the code I have written for this software * can be used freely for any purpose. Any derived versions of this * software must be clearly marked as such, and if the derived work is * incompatible with the protocol description in the RFC file, it must be * called by a name other than "ssh" or "Secure Shell". */ /* * Copyright (c) 1999, 2000, 2001, 2002 Markus Friedl. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef CHANNEL_H #define CHANNEL_H /* Definitions for channel types. */ #define SSH_CHANNEL_X11_LISTENER 1 /* Listening for inet X11 conn. */ #define SSH_CHANNEL_PORT_LISTENER 2 /* Listening on a port. */ #define SSH_CHANNEL_OPENING 3 /* waiting for confirmation */ #define SSH_CHANNEL_OPEN 4 /* normal open two-way channel */ #define SSH_CHANNEL_CLOSED 5 /* waiting for close confirmation */ #define SSH_CHANNEL_AUTH_SOCKET 6 /* authentication socket */ #define SSH_CHANNEL_X11_OPEN 7 /* reading first X11 packet */ #define SSH_CHANNEL_INPUT_DRAINING 8 /* sending remaining data to conn */ #define SSH_CHANNEL_OUTPUT_DRAINING 9 /* sending remaining data to app */ #define SSH_CHANNEL_LARVAL 10 /* larval session */ #define SSH_CHANNEL_RPORT_LISTENER 11 /* Listening to a R-style port */ #define SSH_CHANNEL_CONNECTING 12 #define SSH_CHANNEL_DYNAMIC 13 #define SSH_CHANNEL_ZOMBIE 14 /* Almost dead. */ #define SSH_CHANNEL_MUX_LISTENER 15 /* Listener for mux conn. */ #define SSH_CHANNEL_MUX_CLIENT 16 /* Conn. to mux slave */ #define SSH_CHANNEL_MAX_TYPE 17 struct Channel; typedef struct Channel Channel; typedef void channel_open_fn(int, int, void *); typedef void channel_callback_fn(int, void *); typedef int channel_infilter_fn(struct Channel *, char *, int); typedef void channel_filter_cleanup_fn(int, void *); typedef u_char *channel_outfilter_fn(struct Channel *, u_char **, u_int *); /* Channel success/failure callbacks */ typedef void channel_confirm_cb(int, struct Channel *, void *); typedef void channel_confirm_abandon_cb(struct Channel *, void *); struct channel_confirm { TAILQ_ENTRY(channel_confirm) entry; channel_confirm_cb *cb; channel_confirm_abandon_cb *abandon_cb; void *ctx; }; TAILQ_HEAD(channel_confirms, channel_confirm); /* Context for non-blocking connects */ struct channel_connect { char *host; int port; struct addrinfo *ai, *aitop; }; /* Callbacks for mux channels back into client-specific code */ typedef int mux_callback_fn(struct Channel *); struct Channel { int type; /* channel type/state */ int self; /* my own channel identifier */ int remote_id; /* channel identifier for remote peer */ u_int istate; /* input from channel (state of receive half) */ u_int ostate; /* output to channel (state of transmit half) */ int flags; /* close sent/rcvd */ int rfd; /* read fd */ int wfd; /* write fd */ int efd; /* extended fd */ int sock; /* sock fd */ int ctl_chan; /* control channel (multiplexed connections) */ int isatty; /* rfd is a tty */ int wfd_isatty; /* wfd is a tty */ int client_tty; /* (client) TTY has been requested */ int force_drain; /* force close on iEOF */ int delayed; /* post-select handlers for newly created * channels are delayed until the first call * to a matching pre-select handler. * this way post-select handlers are not * accidenly called if a FD gets reused */ Buffer input; /* data read from socket, to be sent over * encrypted connection */ Buffer output; /* data received over encrypted connection for * send on socket */ Buffer extended; char *path; /* path for unix domain sockets, or host name for forwards */ int listening_port; /* port being listened for forwards */ int host_port; /* remote port to connect for forwards */ char *remote_name; /* remote hostname */ u_int remote_window; u_int remote_maxpacket; u_int local_window; u_int local_window_max; u_int local_consumed; u_int local_maxpacket; + u_int tcpwinsz; + int dynamic_window; int extended_usage; int single_connection; char *ctype; /* type */ /* callback */ channel_open_fn *open_confirm; void *open_confirm_ctx; channel_callback_fn *detach_user; int detach_close; struct channel_confirms status_confirms; /* filter */ channel_infilter_fn *input_filter; channel_outfilter_fn *output_filter; void *filter_ctx; channel_filter_cleanup_fn *filter_cleanup; /* keep boundaries */ int datagram; /* non-blocking connect */ struct channel_connect connect_ctx; /* multiplexing protocol hook, called for each packet received */ mux_callback_fn *mux_rcb; void *mux_ctx; int mux_pause; }; #define CHAN_EXTENDED_IGNORE 0 #define CHAN_EXTENDED_READ 1 #define CHAN_EXTENDED_WRITE 2 /* default window/packet sizes for tcp/x11-fwd-channel */ #define CHAN_SES_PACKET_DEFAULT (32*1024) #define CHAN_SES_WINDOW_DEFAULT (64*CHAN_SES_PACKET_DEFAULT) + #define CHAN_TCP_PACKET_DEFAULT (32*1024) #define CHAN_TCP_WINDOW_DEFAULT (64*CHAN_TCP_PACKET_DEFAULT) + #define CHAN_X11_PACKET_DEFAULT (16*1024) #define CHAN_X11_WINDOW_DEFAULT (4*CHAN_X11_PACKET_DEFAULT) +#define CHAN_HPN_MIN_WINDOW_DEFAULT (2*1024*1024) + /* possible input states */ #define CHAN_INPUT_OPEN 0 #define CHAN_INPUT_WAIT_DRAIN 1 #define CHAN_INPUT_WAIT_OCLOSE 2 #define CHAN_INPUT_CLOSED 3 /* possible output states */ #define CHAN_OUTPUT_OPEN 0 #define CHAN_OUTPUT_WAIT_DRAIN 1 #define CHAN_OUTPUT_WAIT_IEOF 2 #define CHAN_OUTPUT_CLOSED 3 #define CHAN_CLOSE_SENT 0x01 #define CHAN_CLOSE_RCVD 0x02 #define CHAN_EOF_SENT 0x04 #define CHAN_EOF_RCVD 0x08 #define CHAN_LOCAL 0x10 #define CHAN_RBUF 16*1024 /* check whether 'efd' is still in use */ #define CHANNEL_EFD_INPUT_ACTIVE(c) \ (compat20 && c->extended_usage == CHAN_EXTENDED_READ && \ (c->efd != -1 || \ buffer_len(&c->extended) > 0)) #define CHANNEL_EFD_OUTPUT_ACTIVE(c) \ (compat20 && c->extended_usage == CHAN_EXTENDED_WRITE && \ c->efd != -1 && (!(c->flags & (CHAN_EOF_RCVD|CHAN_CLOSE_RCVD)) || \ buffer_len(&c->extended) > 0)) /* channel management */ Channel *channel_by_id(int); Channel *channel_lookup(int); Channel *channel_new(char *, int, int, int, int, u_int, u_int, int, char *, int); void channel_set_fds(int, int, int, int, int, int, int, u_int); void channel_free(Channel *); void channel_free_all(void); void channel_stop_listening(void); void channel_send_open(int); void channel_request_start(int, char *, int); void channel_register_cleanup(int, channel_callback_fn *, int); void channel_register_open_confirm(int, channel_open_fn *, void *); void channel_register_filter(int, channel_infilter_fn *, channel_outfilter_fn *, channel_filter_cleanup_fn *, void *); void channel_register_status_confirm(int, channel_confirm_cb *, channel_confirm_abandon_cb *, void *); void channel_cancel_cleanup(int); int channel_close_fd(int *); void channel_send_window_changes(void); /* protocol handler */ void channel_input_close(int, u_int32_t, void *); void channel_input_close_confirmation(int, u_int32_t, void *); void channel_input_data(int, u_int32_t, void *); void channel_input_extended_data(int, u_int32_t, void *); void channel_input_ieof(int, u_int32_t, void *); void channel_input_oclose(int, u_int32_t, void *); void channel_input_open_confirmation(int, u_int32_t, void *); void channel_input_open_failure(int, u_int32_t, void *); void channel_input_port_open(int, u_int32_t, void *); void channel_input_window_adjust(int, u_int32_t, void *); void channel_input_status_confirm(int, u_int32_t, void *); /* file descriptor handling (read/write) */ void channel_prepare_select(fd_set **, fd_set **, int *, u_int*, int); void channel_after_select(fd_set *, fd_set *); void channel_output_poll(void); int channel_not_very_much_buffered_data(void); void channel_close_all(void); int channel_still_open(void); char *channel_open_message(void); int channel_find_open(void); /* tcp forwarding */ void channel_set_af(int af); void channel_permit_all_opens(void); void channel_add_permitted_opens(char *, int); int channel_add_adm_permitted_opens(char *, int); void channel_clear_permitted_opens(void); void channel_clear_adm_permitted_opens(void); void channel_print_adm_permitted_opens(void); int channel_input_port_forward_request(int, int); Channel *channel_connect_to(const char *, u_short, char *, char *); Channel *channel_connect_stdio_fwd(const char*, u_short, int, int); Channel *channel_connect_by_listen_address(u_short, char *, char *); int channel_request_remote_forwarding(const char *, u_short, const char *, u_short); int channel_setup_local_fwd_listener(const char *, u_short, const char *, u_short, int); void channel_request_rforward_cancel(const char *host, u_short port); int channel_setup_remote_fwd_listener(const char *, u_short, int *, int); int channel_cancel_rport_listener(const char *, u_short); /* x11 forwarding */ int x11_connect_display(void); int x11_create_display_inet(int, int, int, u_int *, int **); void x11_input_open(int, u_int32_t, void *); void x11_request_forwarding_with_spoofing(int, const char *, const char *, const char *); void deny_input_open(int, u_int32_t, void *); /* agent forwarding */ void auth_request_forwarding(void); /* channel close */ int chan_is_dead(Channel *, int); void chan_mark_dead(Channel *); /* channel events */ void chan_rcvd_oclose(Channel *); void chan_rcvd_eow(Channel *); /* SSH2-only */ void chan_read_failed(Channel *); void chan_ibuf_empty(Channel *); void chan_rcvd_ieof(Channel *); void chan_write_failed(Channel *); void chan_obuf_empty(Channel *); +/* hpn handler */ +void channel_set_hpn(int, u_int); + #endif diff --git a/crypto/openssh/cipher.c b/crypto/openssh/cipher.c index bb5c0ac3a2c2..28afa4597f02 100644 --- a/crypto/openssh/cipher.c +++ b/crypto/openssh/cipher.c @@ -1,431 +1,443 @@ /* $OpenBSD: cipher.c,v 1.82 2009/01/26 09:58:15 markus Exp $ */ +/* $FreeBSD$ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved * * As far as I am concerned, the code I have written for this software * can be used freely for any purpose. Any derived versions of this * software must be clearly marked as such, and if the derived work is * incompatible with the protocol description in the RFC file, it must be * called by a name other than "ssh" or "Secure Shell". * * * Copyright (c) 1999 Niels Provos. All rights reserved. * Copyright (c) 1999, 2000 Markus Friedl. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "includes.h" #include #include #include #include #include "xmalloc.h" #include "log.h" #include "cipher.h" /* compatibility with old or broken OpenSSL versions */ #include "openbsd-compat/openssl-compat.h" extern const EVP_CIPHER *evp_ssh1_bf(void); extern const EVP_CIPHER *evp_ssh1_3des(void); extern void ssh1_3des_iv(EVP_CIPHER_CTX *, int, u_char *, int); extern const EVP_CIPHER *evp_aes_128_ctr(void); extern void ssh_aes_ctr_iv(EVP_CIPHER_CTX *, int, u_char *, u_int); struct Cipher { char *name; int number; /* for ssh1 only */ u_int block_size; u_int key_len; u_int discard_len; u_int cbc_mode; const EVP_CIPHER *(*evptype)(void); } ciphers[] = { { "none", SSH_CIPHER_NONE, 8, 0, 0, 0, EVP_enc_null }, { "des", SSH_CIPHER_DES, 8, 8, 0, 1, EVP_des_cbc }, { "3des", SSH_CIPHER_3DES, 8, 16, 0, 1, evp_ssh1_3des }, { "blowfish", SSH_CIPHER_BLOWFISH, 8, 32, 0, 1, evp_ssh1_bf }, { "3des-cbc", SSH_CIPHER_SSH2, 8, 24, 0, 1, EVP_des_ede3_cbc }, { "blowfish-cbc", SSH_CIPHER_SSH2, 8, 16, 0, 1, EVP_bf_cbc }, { "cast128-cbc", SSH_CIPHER_SSH2, 8, 16, 0, 1, EVP_cast5_cbc }, { "arcfour", SSH_CIPHER_SSH2, 8, 16, 0, 0, EVP_rc4 }, { "arcfour128", SSH_CIPHER_SSH2, 8, 16, 1536, 0, EVP_rc4 }, { "arcfour256", SSH_CIPHER_SSH2, 8, 32, 1536, 0, EVP_rc4 }, { "aes128-cbc", SSH_CIPHER_SSH2, 16, 16, 0, 1, EVP_aes_128_cbc }, { "aes192-cbc", SSH_CIPHER_SSH2, 16, 24, 0, 1, EVP_aes_192_cbc }, { "aes256-cbc", SSH_CIPHER_SSH2, 16, 32, 0, 1, EVP_aes_256_cbc }, { "rijndael-cbc@lysator.liu.se", SSH_CIPHER_SSH2, 16, 32, 0, 1, EVP_aes_256_cbc }, { "aes128-ctr", SSH_CIPHER_SSH2, 16, 16, 0, 0, evp_aes_128_ctr }, { "aes192-ctr", SSH_CIPHER_SSH2, 16, 24, 0, 0, evp_aes_128_ctr }, { "aes256-ctr", SSH_CIPHER_SSH2, 16, 32, 0, 0, evp_aes_128_ctr }, #ifdef USE_CIPHER_ACSS { "acss@openssh.org", SSH_CIPHER_SSH2, 16, 5, 0, 0, EVP_acss }, #endif { NULL, SSH_CIPHER_INVALID, 0, 0, 0, 0, NULL } }; /*--*/ u_int cipher_blocksize(const Cipher *c) { return (c->block_size); } u_int cipher_keylen(const Cipher *c) { return (c->key_len); } u_int cipher_get_number(const Cipher *c) { return (c->number); } u_int cipher_is_cbc(const Cipher *c) { return (c->cbc_mode); } u_int cipher_mask_ssh1(int client) { u_int mask = 0; mask |= 1 << SSH_CIPHER_3DES; /* Mandatory */ mask |= 1 << SSH_CIPHER_BLOWFISH; if (client) { mask |= 1 << SSH_CIPHER_DES; } return mask; } Cipher * cipher_by_name(const char *name) { Cipher *c; for (c = ciphers; c->name != NULL; c++) if (strcmp(c->name, name) == 0) return c; return NULL; } Cipher * cipher_by_number(int id) { Cipher *c; for (c = ciphers; c->name != NULL; c++) if (c->number == id) return c; return NULL; } #define CIPHER_SEP "," int ciphers_valid(const char *names) { Cipher *c; char *cipher_list, *cp; char *p; if (names == NULL || strcmp(names, "") == 0) return 0; cipher_list = cp = xstrdup(names); for ((p = strsep(&cp, CIPHER_SEP)); p && *p != '\0'; (p = strsep(&cp, CIPHER_SEP))) { c = cipher_by_name(p); - if (c == NULL || c->number != SSH_CIPHER_SSH2) { +#ifdef NONE_CIPHER_ENABLED + if (c == NULL || (c->number != SSH_CIPHER_SSH2 && + c->number != SSH_CIPHER_NONE)) { +#else + if (c == NULL || (c->number != SSH_CIPHER_SSH2)) { +#endif debug("bad cipher %s [%s]", p, names); xfree(cipher_list); return 0; } else { debug3("cipher ok: %s [%s]", p, names); } } debug3("ciphers ok: [%s]", names); xfree(cipher_list); return 1; } /* * Parses the name of the cipher. Returns the number of the corresponding * cipher, or -1 on error. */ int cipher_number(const char *name) { Cipher *c; if (name == NULL) return -1; for (c = ciphers; c->name != NULL; c++) if (strcasecmp(c->name, name) == 0) return c->number; return -1; } char * cipher_name(int id) { Cipher *c = cipher_by_number(id); return (c==NULL) ? "" : c->name; } void cipher_init(CipherContext *cc, Cipher *cipher, const u_char *key, u_int keylen, const u_char *iv, u_int ivlen, int do_encrypt) { static int dowarn = 1; #ifdef SSH_OLD_EVP EVP_CIPHER *type; #else const EVP_CIPHER *type; int klen; #endif u_char *junk, *discard; if (cipher->number == SSH_CIPHER_DES) { if (dowarn) { error("Warning: use of DES is strongly discouraged " "due to cryptographic weaknesses"); dowarn = 0; } if (keylen > 8) keylen = 8; } cc->plaintext = (cipher->number == SSH_CIPHER_NONE); if (keylen < cipher->key_len) fatal("cipher_init: key length %d is insufficient for %s.", keylen, cipher->name); if (iv != NULL && ivlen < cipher->block_size) fatal("cipher_init: iv length %d is insufficient for %s.", ivlen, cipher->name); cc->cipher = cipher; type = (*cipher->evptype)(); EVP_CIPHER_CTX_init(&cc->evp); #ifdef SSH_OLD_EVP if (type->key_len > 0 && type->key_len != keylen) { debug("cipher_init: set keylen (%d -> %d)", type->key_len, keylen); type->key_len = keylen; } EVP_CipherInit(&cc->evp, type, (u_char *)key, (u_char *)iv, (do_encrypt == CIPHER_ENCRYPT)); #else if (EVP_CipherInit(&cc->evp, type, NULL, (u_char *)iv, (do_encrypt == CIPHER_ENCRYPT)) == 0) fatal("cipher_init: EVP_CipherInit failed for %s", cipher->name); klen = EVP_CIPHER_CTX_key_length(&cc->evp); if (klen > 0 && keylen != (u_int)klen) { debug2("cipher_init: set keylen (%d -> %d)", klen, keylen); if (EVP_CIPHER_CTX_set_key_length(&cc->evp, keylen) == 0) fatal("cipher_init: set keylen failed (%d -> %d)", klen, keylen); } if (EVP_CipherInit(&cc->evp, NULL, (u_char *)key, NULL, -1) == 0) fatal("cipher_init: EVP_CipherInit: set key failed for %s", cipher->name); #endif if (cipher->discard_len > 0) { junk = xmalloc(cipher->discard_len); discard = xmalloc(cipher->discard_len); if (EVP_Cipher(&cc->evp, discard, junk, cipher->discard_len) == 0) fatal("evp_crypt: EVP_Cipher failed during discard"); memset(discard, 0, cipher->discard_len); xfree(junk); xfree(discard); } } void cipher_crypt(CipherContext *cc, u_char *dest, const u_char *src, u_int len) { if (len % cc->cipher->block_size) fatal("cipher_encrypt: bad plaintext length %d", len); if (EVP_Cipher(&cc->evp, dest, (u_char *)src, len) == 0) fatal("evp_crypt: EVP_Cipher failed"); } void cipher_cleanup(CipherContext *cc) { if (EVP_CIPHER_CTX_cleanup(&cc->evp) == 0) error("cipher_cleanup: EVP_CIPHER_CTX_cleanup failed"); } /* * Selects the cipher, and keys if by computing the MD5 checksum of the * passphrase and using the resulting 16 bytes as the key. */ void cipher_set_key_string(CipherContext *cc, Cipher *cipher, const char *passphrase, int do_encrypt) { MD5_CTX md; u_char digest[16]; MD5_Init(&md); MD5_Update(&md, (const u_char *)passphrase, strlen(passphrase)); MD5_Final(digest, &md); cipher_init(cc, cipher, digest, 16, NULL, 0, do_encrypt); memset(digest, 0, sizeof(digest)); memset(&md, 0, sizeof(md)); } /* * Exports an IV from the CipherContext required to export the key * state back from the unprivileged child to the privileged parent * process. */ int cipher_get_keyiv_len(const CipherContext *cc) { Cipher *c = cc->cipher; int ivlen; if (c->number == SSH_CIPHER_3DES) ivlen = 24; else ivlen = EVP_CIPHER_CTX_iv_length(&cc->evp); return (ivlen); } void cipher_get_keyiv(CipherContext *cc, u_char *iv, u_int len) { Cipher *c = cc->cipher; int evplen; switch (c->number) { +#ifdef NONE_CIPHER_ENABLED + case SSH_CIPHER_NONE: +#endif case SSH_CIPHER_SSH2: case SSH_CIPHER_DES: case SSH_CIPHER_BLOWFISH: evplen = EVP_CIPHER_CTX_iv_length(&cc->evp); if (evplen <= 0) return; if ((u_int)evplen != len) fatal("%s: wrong iv length %d != %d", __func__, evplen, len); #ifdef USE_BUILTIN_RIJNDAEL if (c->evptype == evp_rijndael) ssh_rijndael_iv(&cc->evp, 0, iv, len); else #endif if (c->evptype == evp_aes_128_ctr) ssh_aes_ctr_iv(&cc->evp, 0, iv, len); else memcpy(iv, cc->evp.iv, len); break; case SSH_CIPHER_3DES: ssh1_3des_iv(&cc->evp, 0, iv, 24); break; default: fatal("%s: bad cipher %d", __func__, c->number); } } void cipher_set_keyiv(CipherContext *cc, u_char *iv) { Cipher *c = cc->cipher; int evplen = 0; switch (c->number) { +#ifdef NONE_CIPHER_ENABLED + case SSH_CIPHER_NONE: +#endif case SSH_CIPHER_SSH2: case SSH_CIPHER_DES: case SSH_CIPHER_BLOWFISH: evplen = EVP_CIPHER_CTX_iv_length(&cc->evp); if (evplen == 0) return; #ifdef USE_BUILTIN_RIJNDAEL if (c->evptype == evp_rijndael) ssh_rijndael_iv(&cc->evp, 1, iv, evplen); else #endif if (c->evptype == evp_aes_128_ctr) ssh_aes_ctr_iv(&cc->evp, 1, iv, evplen); else memcpy(cc->evp.iv, iv, evplen); break; case SSH_CIPHER_3DES: ssh1_3des_iv(&cc->evp, 1, iv, 24); break; default: fatal("%s: bad cipher %d", __func__, c->number); } } #if OPENSSL_VERSION_NUMBER < 0x00907000L #define EVP_X_STATE(evp) &(evp).c #define EVP_X_STATE_LEN(evp) sizeof((evp).c) #else #define EVP_X_STATE(evp) (evp).cipher_data #define EVP_X_STATE_LEN(evp) (evp).cipher->ctx_size #endif int cipher_get_keycontext(const CipherContext *cc, u_char *dat) { Cipher *c = cc->cipher; int plen = 0; if (c->evptype == EVP_rc4 || c->evptype == EVP_acss) { plen = EVP_X_STATE_LEN(cc->evp); if (dat == NULL) return (plen); memcpy(dat, EVP_X_STATE(cc->evp), plen); } return (plen); } void cipher_set_keycontext(CipherContext *cc, u_char *dat) { Cipher *c = cc->cipher; int plen; if (c->evptype == EVP_rc4 || c->evptype == EVP_acss) { plen = EVP_X_STATE_LEN(cc->evp); memcpy(EVP_X_STATE(cc->evp), dat, plen); } } diff --git a/crypto/openssh/clientloop.c b/crypto/openssh/clientloop.c index f6c1444a385b..05487f266d6a 100644 --- a/crypto/openssh/clientloop.c +++ b/crypto/openssh/clientloop.c @@ -1,2135 +1,2153 @@ /* $OpenBSD: clientloop.c,v 1.231 2011/01/16 12:05:59 djm Exp $ */ +/* $FreeBSD$ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved * The main loop for the interactive session (client side). * * As far as I am concerned, the code I have written for this software * can be used freely for any purpose. Any derived versions of this * software must be clearly marked as such, and if the derived work is * incompatible with the protocol description in the RFC file, it must be * called by a name other than "ssh" or "Secure Shell". * * * Copyright (c) 1999 Theo de Raadt. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * * SSH2 support added by Markus Friedl. * Copyright (c) 1999, 2000, 2001 Markus Friedl. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "includes.h" #include #include #include #ifdef HAVE_SYS_STAT_H # include #endif #ifdef HAVE_SYS_TIME_H # include #endif #include #include #include #ifdef HAVE_PATHS_H #include #endif #include #include #include #include #include #include #include #include #include "openbsd-compat/sys-queue.h" #include "xmalloc.h" #include "ssh.h" #include "ssh1.h" #include "ssh2.h" #include "packet.h" #include "buffer.h" #include "compat.h" #include "channels.h" #include "dispatch.h" #include "key.h" #include "cipher.h" #include "kex.h" #include "log.h" #include "readconf.h" #include "clientloop.h" #include "sshconnect.h" #include "authfd.h" #include "atomicio.h" #include "sshpty.h" #include "misc.h" #include "match.h" #include "msg.h" #include "roaming.h" /* import options */ extern Options options; /* Flag indicating that stdin should be redirected from /dev/null. */ extern int stdin_null_flag; /* Flag indicating that no shell has been requested */ extern int no_shell_flag; /* Control socket */ extern int muxserver_sock; /* XXX use mux_client_cleanup() instead */ /* * Name of the host we are connecting to. This is the name given on the * command line, or the HostName specified for the user-supplied name in a * configuration file. */ extern char *host; /* Force TTY allocation */ extern int force_tty_flag; /* * Flag to indicate that we have received a window change signal which has * not yet been processed. This will cause a message indicating the new * window size to be sent to the server a little later. This is volatile * because this is updated in a signal handler. */ static volatile sig_atomic_t received_window_change_signal = 0; static volatile sig_atomic_t received_signal = 0; /* Flag indicating whether the user's terminal is in non-blocking mode. */ static int in_non_blocking_mode = 0; /* Time when backgrounded control master using ControlPersist should exit */ static time_t control_persist_exit_time = 0; /* Common data for the client loop code. */ volatile sig_atomic_t quit_pending; /* Set non-zero to quit the loop. */ static int escape_char1; /* Escape character. (proto1 only) */ static int escape_pending1; /* Last character was an escape (proto1 only) */ static int last_was_cr; /* Last character was a newline. */ static int exit_status; /* Used to store the command exit status. */ static int stdin_eof; /* EOF has been encountered on stderr. */ static Buffer stdin_buffer; /* Buffer for stdin data. */ static Buffer stdout_buffer; /* Buffer for stdout data. */ static Buffer stderr_buffer; /* Buffer for stderr data. */ static u_int buffer_high; /* Soft max buffer size. */ static int connection_in; /* Connection to server (input). */ static int connection_out; /* Connection to server (output). */ static int need_rekeying; /* Set to non-zero if rekeying is requested. */ static int session_closed; /* In SSH2: login session closed. */ static int x11_refuse_time; /* If >0, refuse x11 opens after this time. */ static void client_init_dispatch(void); int session_ident = -1; int session_resumed = 0; /* Track escape per proto2 channel */ struct escape_filter_ctx { int escape_pending; int escape_char; }; /* Context for channel confirmation replies */ struct channel_reply_ctx { const char *request_type; int id, do_close; }; /* Global request success/failure callbacks */ struct global_confirm { TAILQ_ENTRY(global_confirm) entry; global_confirm_cb *cb; void *ctx; int ref_count; }; TAILQ_HEAD(global_confirms, global_confirm); static struct global_confirms global_confirms = TAILQ_HEAD_INITIALIZER(global_confirms); /*XXX*/ extern Kex *xxx_kex; void ssh_process_session2_setup(int, int, int, Buffer *); /* Restores stdin to blocking mode. */ static void leave_non_blocking(void) { if (in_non_blocking_mode) { unset_nonblock(fileno(stdin)); in_non_blocking_mode = 0; } } /* Puts stdin terminal in non-blocking mode. */ static void enter_non_blocking(void) { in_non_blocking_mode = 1; set_nonblock(fileno(stdin)); } /* * Signal handler for the window change signal (SIGWINCH). This just sets a * flag indicating that the window has changed. */ /*ARGSUSED */ static void window_change_handler(int sig) { received_window_change_signal = 1; signal(SIGWINCH, window_change_handler); } /* * Signal handler for signals that cause the program to terminate. These * signals must be trapped to restore terminal modes. */ /*ARGSUSED */ static void signal_handler(int sig) { received_signal = sig; quit_pending = 1; } /* * Returns current time in seconds from Jan 1, 1970 with the maximum * available resolution. */ static double get_current_time(void) { struct timeval tv; gettimeofday(&tv, NULL); return (double) tv.tv_sec + (double) tv.tv_usec / 1000000.0; } /* * Sets control_persist_exit_time to the absolute time when the * backgrounded control master should exit due to expiry of the * ControlPersist timeout. Sets it to 0 if we are not a backgrounded * control master process, or if there is no ControlPersist timeout. */ static void set_control_persist_exit_time(void) { if (muxserver_sock == -1 || !options.control_persist || options.control_persist_timeout == 0) /* not using a ControlPersist timeout */ control_persist_exit_time = 0; else if (channel_still_open()) { /* some client connections are still open */ if (control_persist_exit_time > 0) debug2("%s: cancel scheduled exit", __func__); control_persist_exit_time = 0; } else if (control_persist_exit_time <= 0) { /* a client connection has recently closed */ control_persist_exit_time = time(NULL) + (time_t)options.control_persist_timeout; debug2("%s: schedule exit in %d seconds", __func__, options.control_persist_timeout); } /* else we are already counting down to the timeout */ } #define SSH_X11_PROTO "MIT-MAGIC-COOKIE-1" void client_x11_get_proto(const char *display, const char *xauth_path, u_int trusted, u_int timeout, char **_proto, char **_data) { char cmd[1024]; char line[512]; char xdisplay[512]; static char proto[512], data[512]; FILE *f; int got_data = 0, generated = 0, do_unlink = 0, i; char *xauthdir, *xauthfile; struct stat st; u_int now; xauthdir = xauthfile = NULL; *_proto = proto; *_data = data; proto[0] = data[0] = '\0'; if (xauth_path == NULL ||(stat(xauth_path, &st) == -1)) { debug("No xauth program."); } else { if (display == NULL) { debug("x11_get_proto: DISPLAY not set"); return; } /* * Handle FamilyLocal case where $DISPLAY does * not match an authorization entry. For this we * just try "xauth list unix:displaynum.screennum". * XXX: "localhost" match to determine FamilyLocal * is not perfect. */ if (strncmp(display, "localhost:", 10) == 0) { snprintf(xdisplay, sizeof(xdisplay), "unix:%s", display + 10); display = xdisplay; } if (trusted == 0) { xauthdir = xmalloc(MAXPATHLEN); xauthfile = xmalloc(MAXPATHLEN); mktemp_proto(xauthdir, MAXPATHLEN); if (mkdtemp(xauthdir) != NULL) { do_unlink = 1; snprintf(xauthfile, MAXPATHLEN, "%s/xauthfile", xauthdir); snprintf(cmd, sizeof(cmd), "%s -f %s generate %s " SSH_X11_PROTO " untrusted timeout %u 2>" _PATH_DEVNULL, xauth_path, xauthfile, display, timeout); debug2("x11_get_proto: %s", cmd); if (system(cmd) == 0) generated = 1; if (x11_refuse_time == 0) { now = time(NULL) + 1; if (UINT_MAX - timeout < now) x11_refuse_time = UINT_MAX; else x11_refuse_time = now + timeout; } } } /* * When in untrusted mode, we read the cookie only if it was * successfully generated as an untrusted one in the step * above. */ if (trusted || generated) { snprintf(cmd, sizeof(cmd), "%s %s%s list %s 2>" _PATH_DEVNULL, xauth_path, generated ? "-f " : "" , generated ? xauthfile : "", display); debug2("x11_get_proto: %s", cmd); f = popen(cmd, "r"); if (f && fgets(line, sizeof(line), f) && sscanf(line, "%*s %511s %511s", proto, data) == 2) got_data = 1; if (f) pclose(f); } else error("Warning: untrusted X11 forwarding setup failed: " "xauth key data not generated"); } if (do_unlink) { unlink(xauthfile); rmdir(xauthdir); } if (xauthdir) xfree(xauthdir); if (xauthfile) xfree(xauthfile); /* * If we didn't get authentication data, just make up some * data. The forwarding code will check the validity of the * response anyway, and substitute this data. The X11 * server, however, will ignore this fake data and use * whatever authentication mechanisms it was using otherwise * for the local connection. */ if (!got_data) { u_int32_t rnd = 0; logit("Warning: No xauth data; " "using fake authentication data for X11 forwarding."); strlcpy(proto, SSH_X11_PROTO, sizeof proto); for (i = 0; i < 16; i++) { if (i % 4 == 0) rnd = arc4random(); snprintf(data + 2 * i, sizeof data - 2 * i, "%02x", rnd & 0xff); rnd >>= 8; } } } /* * This is called when the interactive is entered. This checks if there is * an EOF coming on stdin. We must check this explicitly, as select() does * not appear to wake up when redirecting from /dev/null. */ static void client_check_initial_eof_on_stdin(void) { int len; char buf[1]; /* * If standard input is to be "redirected from /dev/null", we simply * mark that we have seen an EOF and send an EOF message to the * server. Otherwise, we try to read a single character; it appears * that for some files, such /dev/null, select() never wakes up for * read for this descriptor, which means that we never get EOF. This * way we will get the EOF if stdin comes from /dev/null or similar. */ if (stdin_null_flag) { /* Fake EOF on stdin. */ debug("Sending eof."); stdin_eof = 1; packet_start(SSH_CMSG_EOF); packet_send(); } else { enter_non_blocking(); /* Check for immediate EOF on stdin. */ len = read(fileno(stdin), buf, 1); if (len == 0) { /* * EOF. Record that we have seen it and send * EOF to server. */ debug("Sending eof."); stdin_eof = 1; packet_start(SSH_CMSG_EOF); packet_send(); } else if (len > 0) { /* * Got data. We must store the data in the buffer, * and also process it as an escape character if * appropriate. */ if ((u_char) buf[0] == escape_char1) escape_pending1 = 1; else buffer_append(&stdin_buffer, buf, 1); } leave_non_blocking(); } } /* * Make packets from buffered stdin data, and buffer them for sending to the * connection. */ static void client_make_packets_from_stdin_data(void) { u_int len; /* Send buffered stdin data to the server. */ while (buffer_len(&stdin_buffer) > 0 && packet_not_very_much_data_to_write()) { len = buffer_len(&stdin_buffer); /* Keep the packets at reasonable size. */ if (len > packet_get_maxsize()) len = packet_get_maxsize(); packet_start(SSH_CMSG_STDIN_DATA); packet_put_string(buffer_ptr(&stdin_buffer), len); packet_send(); buffer_consume(&stdin_buffer, len); /* If we have a pending EOF, send it now. */ if (stdin_eof && buffer_len(&stdin_buffer) == 0) { packet_start(SSH_CMSG_EOF); packet_send(); } } } /* * Checks if the client window has changed, and sends a packet about it to * the server if so. The actual change is detected elsewhere (by a software * interrupt on Unix); this just checks the flag and sends a message if * appropriate. */ static void client_check_window_change(void) { struct winsize ws; if (! received_window_change_signal) return; /** XXX race */ received_window_change_signal = 0; debug2("client_check_window_change: changed"); if (compat20) { channel_send_window_changes(); } else { if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) < 0) return; packet_start(SSH_CMSG_WINDOW_SIZE); packet_put_int((u_int)ws.ws_row); packet_put_int((u_int)ws.ws_col); packet_put_int((u_int)ws.ws_xpixel); packet_put_int((u_int)ws.ws_ypixel); packet_send(); } } static void client_global_request_reply(int type, u_int32_t seq, void *ctxt) { struct global_confirm *gc; if ((gc = TAILQ_FIRST(&global_confirms)) == NULL) return; if (gc->cb != NULL) gc->cb(type, seq, gc->ctx); if (--gc->ref_count <= 0) { TAILQ_REMOVE(&global_confirms, gc, entry); bzero(gc, sizeof(*gc)); xfree(gc); } packet_set_alive_timeouts(0); } static void server_alive_check(void) { if (packet_inc_alive_timeouts() > options.server_alive_count_max) { logit("Timeout, server %s not responding.", host); cleanup_exit(255); } packet_start(SSH2_MSG_GLOBAL_REQUEST); packet_put_cstring("keepalive@openssh.com"); packet_put_char(1); /* boolean: want reply */ packet_send(); /* Insert an empty placeholder to maintain ordering */ client_register_global_confirm(NULL, NULL); } /* * Waits until the client can do something (some data becomes available on * one of the file descriptors). */ static void client_wait_until_can_do_something(fd_set **readsetp, fd_set **writesetp, int *maxfdp, u_int *nallocp, int rekeying) { struct timeval tv, *tvp; int timeout_secs; int ret; /* Add any selections by the channel mechanism. */ channel_prepare_select(readsetp, writesetp, maxfdp, nallocp, rekeying); if (!compat20) { /* Read from the connection, unless our buffers are full. */ if (buffer_len(&stdout_buffer) < buffer_high && buffer_len(&stderr_buffer) < buffer_high && channel_not_very_much_buffered_data()) FD_SET(connection_in, *readsetp); /* * Read from stdin, unless we have seen EOF or have very much * buffered data to send to the server. */ if (!stdin_eof && packet_not_very_much_data_to_write()) FD_SET(fileno(stdin), *readsetp); /* Select stdout/stderr if have data in buffer. */ if (buffer_len(&stdout_buffer) > 0) FD_SET(fileno(stdout), *writesetp); if (buffer_len(&stderr_buffer) > 0) FD_SET(fileno(stderr), *writesetp); } else { /* channel_prepare_select could have closed the last channel */ if (session_closed && !channel_still_open() && !packet_have_data_to_write()) { /* clear mask since we did not call select() */ memset(*readsetp, 0, *nallocp); memset(*writesetp, 0, *nallocp); return; } else { FD_SET(connection_in, *readsetp); } } /* Select server connection if have data to write to the server. */ if (packet_have_data_to_write()) FD_SET(connection_out, *writesetp); /* * Wait for something to happen. This will suspend the process until * some selected descriptor can be read, written, or has some other * event pending, or a timeout expires. */ timeout_secs = INT_MAX; /* we use INT_MAX to mean no timeout */ if (options.server_alive_interval > 0 && compat20) timeout_secs = options.server_alive_interval; set_control_persist_exit_time(); if (control_persist_exit_time > 0) { timeout_secs = MIN(timeout_secs, control_persist_exit_time - time(NULL)); if (timeout_secs < 0) timeout_secs = 0; } if (timeout_secs == INT_MAX) tvp = NULL; else { tv.tv_sec = timeout_secs; tv.tv_usec = 0; tvp = &tv; } ret = select((*maxfdp)+1, *readsetp, *writesetp, NULL, tvp); if (ret < 0) { char buf[100]; /* * We have to clear the select masks, because we return. * We have to return, because the mainloop checks for the flags * set by the signal handlers. */ memset(*readsetp, 0, *nallocp); memset(*writesetp, 0, *nallocp); if (errno == EINTR) return; /* Note: we might still have data in the buffers. */ snprintf(buf, sizeof buf, "select: %s\r\n", strerror(errno)); buffer_append(&stderr_buffer, buf, strlen(buf)); quit_pending = 1; } else if (ret == 0) server_alive_check(); } static void client_suspend_self(Buffer *bin, Buffer *bout, Buffer *berr) { /* Flush stdout and stderr buffers. */ if (buffer_len(bout) > 0) atomicio(vwrite, fileno(stdout), buffer_ptr(bout), buffer_len(bout)); if (buffer_len(berr) > 0) atomicio(vwrite, fileno(stderr), buffer_ptr(berr), buffer_len(berr)); leave_raw_mode(force_tty_flag); /* * Free (and clear) the buffer to reduce the amount of data that gets * written to swap. */ buffer_free(bin); buffer_free(bout); buffer_free(berr); /* Send the suspend signal to the program itself. */ kill(getpid(), SIGTSTP); /* Reset window sizes in case they have changed */ received_window_change_signal = 1; /* OK, we have been continued by the user. Reinitialize buffers. */ buffer_init(bin); buffer_init(bout); buffer_init(berr); enter_raw_mode(force_tty_flag); } static void client_process_net_input(fd_set *readset) { int len, cont = 0; char buf[SSH_IOBUFSZ]; /* * Read input from the server, and add any such data to the buffer of * the packet subsystem. */ if (FD_ISSET(connection_in, readset)) { /* Read as much as possible. */ len = roaming_read(connection_in, buf, sizeof(buf), &cont); if (len == 0 && cont == 0) { /* * Received EOF. The remote host has closed the * connection. */ snprintf(buf, sizeof buf, "Connection to %.300s closed by remote host.\r\n", host); buffer_append(&stderr_buffer, buf, strlen(buf)); quit_pending = 1; return; } /* * There is a kernel bug on Solaris that causes select to * sometimes wake up even though there is no data available. */ if (len < 0 && (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK)) len = 0; if (len < 0) { /* * An error has encountered. Perhaps there is a * network problem. */ snprintf(buf, sizeof buf, "Read from remote host %.300s: %.100s\r\n", host, strerror(errno)); buffer_append(&stderr_buffer, buf, strlen(buf)); quit_pending = 1; return; } packet_process_incoming(buf, len); } } static void client_status_confirm(int type, Channel *c, void *ctx) { struct channel_reply_ctx *cr = (struct channel_reply_ctx *)ctx; char errmsg[256]; int tochan; /* XXX supress on mux _client_ quietmode */ tochan = options.log_level >= SYSLOG_LEVEL_ERROR && c->ctl_chan != -1 && c->extended_usage == CHAN_EXTENDED_WRITE; if (type == SSH2_MSG_CHANNEL_SUCCESS) { debug2("%s request accepted on channel %d", cr->request_type, c->self); } else if (type == SSH2_MSG_CHANNEL_FAILURE) { if (tochan) { snprintf(errmsg, sizeof(errmsg), "%s request failed\r\n", cr->request_type); } else { snprintf(errmsg, sizeof(errmsg), "%s request failed on channel %d", cr->request_type, c->self); } /* If error occurred on primary session channel, then exit */ if (cr->do_close && c->self == session_ident) fatal("%s", errmsg); /* If error occurred on mux client, append to their stderr */ if (tochan) buffer_append(&c->extended, errmsg, strlen(errmsg)); else error("%s", errmsg); if (cr->do_close) { chan_read_failed(c); chan_write_failed(c); } } xfree(cr); } static void client_abandon_status_confirm(Channel *c, void *ctx) { xfree(ctx); } static void client_expect_confirm(int id, const char *request, int do_close) { struct channel_reply_ctx *cr = xmalloc(sizeof(*cr)); cr->request_type = request; cr->do_close = do_close; channel_register_status_confirm(id, client_status_confirm, client_abandon_status_confirm, cr); } void client_register_global_confirm(global_confirm_cb *cb, void *ctx) { struct global_confirm *gc, *last_gc; /* Coalesce identical callbacks */ last_gc = TAILQ_LAST(&global_confirms, global_confirms); if (last_gc && last_gc->cb == cb && last_gc->ctx == ctx) { if (++last_gc->ref_count >= INT_MAX) fatal("%s: last_gc->ref_count = %d", __func__, last_gc->ref_count); return; } gc = xmalloc(sizeof(*gc)); gc->cb = cb; gc->ctx = ctx; gc->ref_count = 1; TAILQ_INSERT_TAIL(&global_confirms, gc, entry); } static void process_cmdline(void) { void (*handler)(int); char *s, *cmd, *cancel_host; int delete = 0; int local = 0, remote = 0, dynamic = 0; int cancel_port; Forward fwd; bzero(&fwd, sizeof(fwd)); fwd.listen_host = fwd.connect_host = NULL; leave_raw_mode(force_tty_flag); handler = signal(SIGINT, SIG_IGN); cmd = s = read_passphrase("\r\nssh> ", RP_ECHO); if (s == NULL) goto out; while (isspace(*s)) s++; if (*s == '-') s++; /* Skip cmdline '-', if any */ if (*s == '\0') goto out; if (*s == 'h' || *s == 'H' || *s == '?') { logit("Commands:"); logit(" -L[bind_address:]port:host:hostport " "Request local forward"); logit(" -R[bind_address:]port:host:hostport " "Request remote forward"); logit(" -D[bind_address:]port " "Request dynamic forward"); logit(" -KR[bind_address:]port " "Cancel remote forward"); if (!options.permit_local_command) goto out; logit(" !args " "Execute local command"); goto out; } if (*s == '!' && options.permit_local_command) { s++; ssh_local_cmd(s); goto out; } if (*s == 'K') { delete = 1; s++; } if (*s == 'L') local = 1; else if (*s == 'R') remote = 1; else if (*s == 'D') dynamic = 1; else { logit("Invalid command."); goto out; } if ((local || dynamic) && delete) { logit("Not supported."); goto out; } if (remote && delete && !compat20) { logit("Not supported for SSH protocol version 1."); goto out; } while (isspace(*++s)) ; /* XXX update list of forwards in options */ if (delete) { cancel_port = 0; cancel_host = hpdelim(&s); /* may be NULL */ if (s != NULL) { cancel_port = a2port(s); cancel_host = cleanhostname(cancel_host); } else { cancel_port = a2port(cancel_host); cancel_host = NULL; } if (cancel_port <= 0) { logit("Bad forwarding close port"); goto out; } channel_request_rforward_cancel(cancel_host, cancel_port); } else { if (!parse_forward(&fwd, s, dynamic, remote)) { logit("Bad forwarding specification."); goto out; } if (local || dynamic) { if (channel_setup_local_fwd_listener(fwd.listen_host, fwd.listen_port, fwd.connect_host, fwd.connect_port, options.gateway_ports) < 0) { logit("Port forwarding failed."); goto out; } } else { if (channel_request_remote_forwarding(fwd.listen_host, fwd.listen_port, fwd.connect_host, fwd.connect_port) < 0) { logit("Port forwarding failed."); goto out; } } logit("Forwarding port."); } out: signal(SIGINT, handler); enter_raw_mode(force_tty_flag); if (cmd) xfree(cmd); if (fwd.listen_host != NULL) xfree(fwd.listen_host); if (fwd.connect_host != NULL) xfree(fwd.connect_host); } /* * Process the characters one by one, call with c==NULL for proto1 case. */ static int process_escapes(Channel *c, 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; int *escape_pendingp, escape_char; struct escape_filter_ctx *efc; if (c == NULL) { escape_pendingp = &escape_pending1; escape_char = escape_char1; } else { if (c->filter_ctx == NULL) return 0; efc = (struct escape_filter_ctx *)c->filter_ctx; escape_pendingp = &efc->escape_pending; escape_char = efc->escape_char; } if (len <= 0) return (0); for (i = 0; i < (u_int)len; i++) { /* Get one character at a time. */ ch = buf[i]; if (*escape_pendingp) { /* We have previously seen an escape character. */ /* Clear the flag now. */ *escape_pendingp = 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)); if (c && c->ctl_chan != -1) { chan_read_failed(c); chan_write_failed(c); return 0; } else quit_pending = 1; return -1; case 'Z' - 64: /* XXX support this for mux clients */ if (c && c->ctl_chan != -1) { noescape: snprintf(string, sizeof string, "%c%c escape not available to " "multiplexed sessions\r\n", escape_char, ch); buffer_append(berr, string, strlen(string)); continue; } /* Suspend the program. Inform 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 '&': if (c && c->ctl_chan != -1) goto noescape; /* * Detach the program (continue to serve * connections, but put in background and no * more new connections). */ /* Restore tty modes. */ leave_raw_mode(force_tty_flag); /* 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 '?': if (c && c->ctl_chan != -1) { snprintf(string, sizeof string, "%c?\r\n\ Supported escape sequences:\r\n\ %c. - terminate session\r\n\ %cB - send a BREAK to the remote system\r\n\ %cR - Request rekey (SSH protocol 2 only)\r\n\ %c# - list forwarded connections\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); } else { snprintf(string, sizeof string, "%c?\r\n\ Supported escape sequences:\r\n\ %c. - terminate connection (and any multiplexed sessions)\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': if (c && c->ctl_chan != -1) goto noescape; 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_pendingp = 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 client_process_input(fd_set *readset) { int len; char buf[SSH_IOBUFSZ]; /* Read input from stdin. */ if (FD_ISSET(fileno(stdin), readset)) { /* Read as much as possible. */ len = read(fileno(stdin), buf, sizeof(buf)); if (len < 0 && (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK)) return; /* we'll try again later */ if (len <= 0) { /* * Received EOF or error. They are treated * similarly, except that an error message is printed * if it was an error condition. */ if (len < 0) { snprintf(buf, sizeof buf, "read: %.100s\r\n", strerror(errno)); buffer_append(&stderr_buffer, buf, strlen(buf)); } /* Mark that we have seen EOF. */ stdin_eof = 1; /* * Send an EOF message to the server unless there is * data in the buffer. If there is data in the * buffer, no message will be sent now. Code * elsewhere will send the EOF when the buffer * becomes empty if stdin_eof is set. */ if (buffer_len(&stdin_buffer) == 0) { packet_start(SSH_CMSG_EOF); packet_send(); } } else if (escape_char1 == SSH_ESCAPECHAR_NONE) { /* * Normal successful read, and no escape character. * Just append the data to buffer. */ buffer_append(&stdin_buffer, buf, len); } else { /* * Normal, successful read. But we have an escape * character and have to process the characters one * by one. */ if (process_escapes(NULL, &stdin_buffer, &stdout_buffer, &stderr_buffer, buf, len) == -1) return; } } } static void client_process_output(fd_set *writeset) { int len; char buf[100]; /* Write buffered output to stdout. */ if (FD_ISSET(fileno(stdout), writeset)) { /* Write as much data as possible. */ len = write(fileno(stdout), buffer_ptr(&stdout_buffer), buffer_len(&stdout_buffer)); if (len <= 0) { if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) len = 0; else { /* * An error or EOF was encountered. Put an * error message to stderr buffer. */ snprintf(buf, sizeof buf, "write stdout: %.50s\r\n", strerror(errno)); buffer_append(&stderr_buffer, buf, strlen(buf)); quit_pending = 1; return; } } /* Consume printed data from the buffer. */ buffer_consume(&stdout_buffer, len); } /* Write buffered output to stderr. */ if (FD_ISSET(fileno(stderr), writeset)) { /* Write as much data as possible. */ len = write(fileno(stderr), buffer_ptr(&stderr_buffer), buffer_len(&stderr_buffer)); if (len <= 0) { if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) len = 0; else { /* * EOF or error, but can't even print * error message. */ quit_pending = 1; return; } } /* Consume printed characters from the buffer. */ buffer_consume(&stderr_buffer, len); } } /* * Get packets from the connection input buffer, and process them as long as * there are packets available. * * Any unknown packets received during the actual * session cause the session to terminate. This is * intended to make debugging easier since no * confirmations are sent. Any compatible protocol * extensions must be negotiated during the * preparatory phase. */ static void client_process_buffered_input_packets(void) { dispatch_run(DISPATCH_NONBLOCK, &quit_pending, compat20 ? xxx_kex : NULL); } /* scan buf[] for '~' before sending data to the peer */ /* Helper: allocate a new escape_filter_ctx and fill in its escape char */ void * client_new_escape_filter_ctx(int escape_char) { struct escape_filter_ctx *ret; ret = xmalloc(sizeof(*ret)); ret->escape_pending = 0; ret->escape_char = escape_char; return (void *)ret; } /* Free the escape filter context on channel free */ void client_filter_cleanup(int cid, void *ctx) { xfree(ctx); } int client_simple_escape_filter(Channel *c, char *buf, int len) { if (c->extended_usage != CHAN_EXTENDED_WRITE) return 0; return process_escapes(c, &c->input, &c->output, &c->extended, buf, len); } static void client_channel_closed(int id, void *arg) { channel_cancel_cleanup(id); session_closed = 1; leave_raw_mode(force_tty_flag); } /* * Implements the interactive session with the server. This is called after * the user has been authenticated, and a command has been started on the * remote host. If escape_char != SSH_ESCAPECHAR_NONE, it is the character * used as an escape character for terminating or suspending the session. */ int client_loop(int have_pty, int escape_char_arg, int ssh2_chan_id) { fd_set *readset = NULL, *writeset = NULL; double start_time, total_time; int max_fd = 0, max_fd2 = 0, len, rekeying = 0; u_int64_t ibytes, obytes; u_int nalloc = 0; char buf[100]; debug("Entering interactive session."); start_time = get_current_time(); /* Initialize variables. */ escape_pending1 = 0; last_was_cr = 1; exit_status = -1; stdin_eof = 0; buffer_high = 64 * 1024; connection_in = packet_get_connection_in(); connection_out = packet_get_connection_out(); max_fd = MAX(connection_in, connection_out); if (!compat20) { /* enable nonblocking unless tty */ if (!isatty(fileno(stdin))) set_nonblock(fileno(stdin)); if (!isatty(fileno(stdout))) set_nonblock(fileno(stdout)); if (!isatty(fileno(stderr))) set_nonblock(fileno(stderr)); max_fd = MAX(max_fd, fileno(stdin)); max_fd = MAX(max_fd, fileno(stdout)); max_fd = MAX(max_fd, fileno(stderr)); } quit_pending = 0; escape_char1 = escape_char_arg; /* Initialize buffers. */ buffer_init(&stdin_buffer); buffer_init(&stdout_buffer); buffer_init(&stderr_buffer); client_init_dispatch(); /* * Set signal handlers, (e.g. to restore non-blocking mode) * but don't overwrite SIG_IGN, matches behaviour from rsh(1) */ if (signal(SIGHUP, SIG_IGN) != SIG_IGN) signal(SIGHUP, signal_handler); if (signal(SIGINT, SIG_IGN) != SIG_IGN) signal(SIGINT, signal_handler); if (signal(SIGQUIT, SIG_IGN) != SIG_IGN) signal(SIGQUIT, signal_handler); if (signal(SIGTERM, SIG_IGN) != SIG_IGN) signal(SIGTERM, signal_handler); signal(SIGWINCH, window_change_handler); if (have_pty) enter_raw_mode(force_tty_flag); if (compat20) { session_ident = ssh2_chan_id; if (escape_char_arg != SSH_ESCAPECHAR_NONE) channel_register_filter(session_ident, client_simple_escape_filter, NULL, client_filter_cleanup, client_new_escape_filter_ctx(escape_char_arg)); if (session_ident != -1) channel_register_cleanup(session_ident, client_channel_closed, 0); } else { /* Check if we should immediately send eof on stdin. */ client_check_initial_eof_on_stdin(); } /* Main loop of the client for the interactive session mode. */ while (!quit_pending) { /* Process buffered packets sent by the server. */ client_process_buffered_input_packets(); if (compat20 && session_closed && !channel_still_open()) break; rekeying = (xxx_kex != NULL && !xxx_kex->done); if (rekeying) { debug("rekeying in progress"); } else { /* * Make packets of buffered stdin data, and buffer * them for sending to the server. */ if (!compat20) client_make_packets_from_stdin_data(); /* * Make packets from buffered channel data, and * enqueue them for sending to the server. */ if (packet_not_very_much_data_to_write()) channel_output_poll(); /* * Check if the window size has changed, and buffer a * message about it to the server if so. */ client_check_window_change(); if (quit_pending) break; } /* * Wait until we have something to do (something becomes * available on one of the descriptors). */ max_fd2 = max_fd; client_wait_until_can_do_something(&readset, &writeset, &max_fd2, &nalloc, rekeying); if (quit_pending) break; /* Do channel operations unless rekeying in progress. */ if (!rekeying) { channel_after_select(readset, writeset); if (need_rekeying || packet_need_rekeying()) { debug("need rekeying"); xxx_kex->done = 0; kex_send_kexinit(xxx_kex); need_rekeying = 0; } } /* Buffer input from the connection. */ client_process_net_input(readset); if (quit_pending) break; if (!compat20) { /* Buffer data from stdin */ client_process_input(readset); /* * Process output to stdout and stderr. Output to * the connection is processed elsewhere (above). */ client_process_output(writeset); } if (session_resumed) { connection_in = packet_get_connection_in(); connection_out = packet_get_connection_out(); max_fd = MAX(max_fd, connection_out); max_fd = MAX(max_fd, connection_in); session_resumed = 0; } /* * Send as much buffered packet data as possible to the * sender. */ if (FD_ISSET(connection_out, writeset)) packet_write_poll(); /* * If we are a backgrounded control master, and the * timeout has expired without any active client * connections, then quit. */ if (control_persist_exit_time > 0) { if (time(NULL) >= control_persist_exit_time) { debug("ControlPersist timeout expired"); break; } } } if (readset) xfree(readset); if (writeset) xfree(writeset); /* Terminate the session. */ /* Stop watching for window change. */ signal(SIGWINCH, SIG_DFL); if (compat20) { packet_start(SSH2_MSG_DISCONNECT); packet_put_int(SSH2_DISCONNECT_BY_APPLICATION); packet_put_cstring("disconnected by user"); packet_put_cstring(""); /* language tag */ packet_send(); packet_write_wait(); } channel_free_all(); if (have_pty) leave_raw_mode(force_tty_flag); /* restore blocking io */ if (!isatty(fileno(stdin))) unset_nonblock(fileno(stdin)); if (!isatty(fileno(stdout))) unset_nonblock(fileno(stdout)); if (!isatty(fileno(stderr))) unset_nonblock(fileno(stderr)); /* * If there was no shell or command requested, there will be no remote * exit status to be returned. In that case, clear error code if the * connection was deliberately terminated at this end. */ if (no_shell_flag && received_signal == SIGTERM) { received_signal = 0; exit_status = 0; } if (received_signal) fatal("Killed by signal %d.", (int) received_signal); /* * In interactive mode (with pseudo tty) display a message indicating * that the connection has been closed. */ if (have_pty && options.log_level != SYSLOG_LEVEL_QUIET) { snprintf(buf, sizeof buf, "Connection to %.64s closed.\r\n", host); buffer_append(&stderr_buffer, buf, strlen(buf)); } /* Output any buffered data for stdout. */ if (buffer_len(&stdout_buffer) > 0) { len = atomicio(vwrite, fileno(stdout), buffer_ptr(&stdout_buffer), buffer_len(&stdout_buffer)); if (len < 0 || (u_int)len != buffer_len(&stdout_buffer)) error("Write failed flushing stdout buffer."); else buffer_consume(&stdout_buffer, len); } /* Output any buffered data for stderr. */ if (buffer_len(&stderr_buffer) > 0) { len = atomicio(vwrite, fileno(stderr), buffer_ptr(&stderr_buffer), buffer_len(&stderr_buffer)); if (len < 0 || (u_int)len != buffer_len(&stderr_buffer)) error("Write failed flushing stderr buffer."); else buffer_consume(&stderr_buffer, len); } /* Clear and free any buffers. */ memset(buf, 0, sizeof(buf)); buffer_free(&stdin_buffer); buffer_free(&stdout_buffer); buffer_free(&stderr_buffer); /* Report bytes transferred, and transfer rates. */ total_time = get_current_time() - start_time; packet_get_state(MODE_IN, NULL, NULL, NULL, &ibytes); packet_get_state(MODE_OUT, NULL, NULL, NULL, &obytes); verbose("Transferred: sent %llu, received %llu bytes, in %.1f seconds", (unsigned long long)obytes, (unsigned long long)ibytes, total_time); if (total_time > 0) verbose("Bytes per second: sent %.1f, received %.1f", obytes / total_time, ibytes / total_time); /* Return the exit status of the program. */ debug("Exit status %d", exit_status); return exit_status; } /*********/ static void client_input_stdout_data(int type, u_int32_t seq, void *ctxt) { u_int data_len; char *data = packet_get_string(&data_len); packet_check_eom(); buffer_append(&stdout_buffer, data, data_len); memset(data, 0, data_len); xfree(data); } static void client_input_stderr_data(int type, u_int32_t seq, void *ctxt) { u_int data_len; char *data = packet_get_string(&data_len); packet_check_eom(); buffer_append(&stderr_buffer, data, data_len); memset(data, 0, data_len); xfree(data); } static void client_input_exit_status(int type, u_int32_t seq, void *ctxt) { exit_status = packet_get_int(); packet_check_eom(); /* Acknowledge the exit. */ packet_start(SSH_CMSG_EXIT_CONFIRMATION); packet_send(); /* * Must wait for packet to be sent since we are * exiting the loop. */ packet_write_wait(); /* Flag that we want to exit. */ quit_pending = 1; } static void client_input_agent_open(int type, u_int32_t seq, void *ctxt) { Channel *c = NULL; int remote_id, sock; /* Read the remote channel number from the message. */ remote_id = packet_get_int(); packet_check_eom(); /* * Get a connection to the local authentication agent (this may again * get forwarded). */ sock = ssh_get_authentication_socket(); /* * If we could not connect the agent, send an error message back to * the server. This should never happen unless the agent dies, * because authentication forwarding is only enabled if we have an * agent. */ if (sock >= 0) { c = channel_new("", SSH_CHANNEL_OPEN, sock, sock, -1, 0, 0, 0, "authentication agent connection", 1); c->remote_id = remote_id; c->force_drain = 1; } if (c == NULL) { packet_start(SSH_MSG_CHANNEL_OPEN_FAILURE); packet_put_int(remote_id); } else { /* Send a confirmation to the remote host. */ debug("Forwarding authentication connection."); packet_start(SSH_MSG_CHANNEL_OPEN_CONFIRMATION); packet_put_int(remote_id); packet_put_int(c->self); } packet_send(); } static Channel * client_request_forwarded_tcpip(const char *request_type, int rchan) { Channel *c = NULL; char *listen_address, *originator_address; u_short listen_port, originator_port; /* Get rest of the packet */ listen_address = packet_get_string(NULL); listen_port = packet_get_int(); originator_address = packet_get_string(NULL); originator_port = packet_get_int(); packet_check_eom(); debug("client_request_forwarded_tcpip: listen %s port %d, " "originator %s port %d", listen_address, listen_port, originator_address, originator_port); c = channel_connect_by_listen_address(listen_port, "forwarded-tcpip", originator_address); xfree(originator_address); xfree(listen_address); return c; } static Channel * client_request_x11(const char *request_type, int rchan) { Channel *c = NULL; char *originator; u_short originator_port; int sock; if (!options.forward_x11) { error("Warning: ssh server tried X11 forwarding."); error("Warning: this is probably a break-in attempt by a " "malicious server."); return NULL; } if (x11_refuse_time != 0 && time(NULL) >= x11_refuse_time) { verbose("Rejected X11 connection after ForwardX11Timeout " "expired"); return NULL; } originator = packet_get_string(NULL); if (datafellows & SSH_BUG_X11FWD) { debug2("buggy server: x11 request w/o originator_port"); originator_port = 0; } else { originator_port = packet_get_int(); } packet_check_eom(); /* XXX check permission */ debug("client_request_x11: request from %s %d", originator, originator_port); xfree(originator); sock = x11_connect_display(); if (sock < 0) return NULL; - c = channel_new("x11", - SSH_CHANNEL_X11_OPEN, sock, sock, -1, - CHAN_TCP_WINDOW_DEFAULT, CHAN_X11_PACKET_DEFAULT, 0, "x11", 1); + if (options.hpn_disabled) + c = channel_new("x11", SSH_CHANNEL_X11_OPEN, sock, sock, -1, + CHAN_TCP_WINDOW_DEFAULT, CHAN_X11_PACKET_DEFAULT, + 0, "x11", 1); + else + c = channel_new("x11", SSH_CHANNEL_X11_OPEN, sock, sock, -1, + options.hpn_buffer_size, CHAN_X11_PACKET_DEFAULT, + 0, "x11", 1); c->force_drain = 1; return c; } static Channel * client_request_agent(const char *request_type, int rchan) { Channel *c = NULL; int sock; if (!options.forward_agent) { error("Warning: ssh server tried agent forwarding."); error("Warning: this is probably a break-in attempt by a " "malicious server."); return NULL; } sock = ssh_get_authentication_socket(); if (sock < 0) return NULL; - c = channel_new("authentication agent connection", - SSH_CHANNEL_OPEN, sock, sock, -1, - CHAN_X11_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, 0, - "authentication agent connection", 1); + if (options.hpn_disabled) + c = channel_new("authentication agent connection", + SSH_CHANNEL_OPEN, sock, sock, -1, + CHAN_X11_WINDOW_DEFAULT, CHAN_TCP_WINDOW_DEFAULT, 0, + "authentication agent connection", 1); + else + c = channel_new("authentication agent connection", + SSH_CHANNEL_OPEN, sock, sock, -1, + options.hpn_buffer_size, options.hpn_buffer_size, 0, + "authentication agent connection", 1); c->force_drain = 1; return c; } int client_request_tun_fwd(int tun_mode, int local_tun, int remote_tun) { Channel *c; int fd; if (tun_mode == SSH_TUNMODE_NO) return 0; if (!compat20) { error("Tunnel forwarding is not supported for protocol 1"); return -1; } debug("Requesting tun unit %d in mode %d", local_tun, tun_mode); /* Open local tunnel device */ if ((fd = tun_open(local_tun, tun_mode)) == -1) { error("Tunnel device open failed."); return -1; } - c = channel_new("tun", SSH_CHANNEL_OPENING, fd, fd, -1, - CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, 0, "tun", 1); + if (options.hpn_disabled) + c = channel_new("tun", SSH_CHANNEL_OPENING, fd, fd, -1, + CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, + 0, "tun", 1); + else + c = channel_new("tun", SSH_CHANNEL_OPENING, fd, fd, -1, + options.hpn_buffer_size, CHAN_TCP_PACKET_DEFAULT, + 0, "tun", 1); c->datagram = 1; #if defined(SSH_TUN_FILTER) if (options.tun_open == SSH_TUNMODE_POINTOPOINT) channel_register_filter(c->self, sys_tun_infilter, sys_tun_outfilter, NULL, NULL); #endif packet_start(SSH2_MSG_CHANNEL_OPEN); packet_put_cstring("tun@openssh.com"); packet_put_int(c->self); packet_put_int(c->local_window_max); packet_put_int(c->local_maxpacket); packet_put_int(tun_mode); packet_put_int(remote_tun); packet_send(); return 0; } /* XXXX move to generic input handler */ static void client_input_channel_open(int type, u_int32_t seq, void *ctxt) { Channel *c = NULL; char *ctype; int rchan; u_int rmaxpack, rwindow, len; ctype = packet_get_string(&len); rchan = packet_get_int(); rwindow = packet_get_int(); rmaxpack = packet_get_int(); debug("client_input_channel_open: ctype %s rchan %d win %d max %d", ctype, rchan, rwindow, rmaxpack); if (strcmp(ctype, "forwarded-tcpip") == 0) { c = client_request_forwarded_tcpip(ctype, rchan); } else if (strcmp(ctype, "x11") == 0) { c = client_request_x11(ctype, rchan); } else if (strcmp(ctype, "auth-agent@openssh.com") == 0) { c = client_request_agent(ctype, rchan); } /* XXX duplicate : */ if (c != NULL) { debug("confirm %s", ctype); c->remote_id = rchan; c->remote_window = rwindow; c->remote_maxpacket = rmaxpack; if (c->type != SSH_CHANNEL_CONNECTING) { packet_start(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION); packet_put_int(c->remote_id); packet_put_int(c->self); packet_put_int(c->local_window); packet_put_int(c->local_maxpacket); packet_send(); } } else { debug("failure %s", ctype); packet_start(SSH2_MSG_CHANNEL_OPEN_FAILURE); packet_put_int(rchan); packet_put_int(SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED); if (!(datafellows & SSH_BUG_OPENFAILURE)) { packet_put_cstring("open failed"); packet_put_cstring(""); } packet_send(); } xfree(ctype); } static void client_input_channel_req(int type, u_int32_t seq, void *ctxt) { Channel *c = NULL; int exitval, id, reply, success = 0; char *rtype; id = packet_get_int(); rtype = packet_get_string(NULL); reply = packet_get_char(); debug("client_input_channel_req: channel %d rtype %s reply %d", id, rtype, reply); if (id == -1) { error("client_input_channel_req: request for channel -1"); } else if ((c = channel_lookup(id)) == NULL) { error("client_input_channel_req: channel %d: " "unknown channel", id); } else if (strcmp(rtype, "eow@openssh.com") == 0) { packet_check_eom(); chan_rcvd_eow(c); } else if (strcmp(rtype, "exit-status") == 0) { exitval = packet_get_int(); if (c->ctl_chan != -1) { mux_exit_message(c, exitval); success = 1; } else if (id == session_ident) { /* Record exit value of local session */ success = 1; exit_status = exitval; } else { /* Probably for a mux channel that has already closed */ debug("%s: no sink for exit-status on channel %d", __func__, id); } packet_check_eom(); } if (reply && c != NULL) { packet_start(success ? SSH2_MSG_CHANNEL_SUCCESS : SSH2_MSG_CHANNEL_FAILURE); packet_put_int(c->remote_id); packet_send(); } xfree(rtype); } static void client_input_global_request(int type, u_int32_t seq, void *ctxt) { char *rtype; int want_reply; int success = 0; rtype = packet_get_string(NULL); want_reply = packet_get_char(); debug("client_input_global_request: rtype %s want_reply %d", rtype, want_reply); if (want_reply) { packet_start(success ? SSH2_MSG_REQUEST_SUCCESS : SSH2_MSG_REQUEST_FAILURE); packet_send(); packet_write_wait(); } xfree(rtype); } void client_session2_setup(int id, int want_tty, int want_subsystem, const char *term, struct termios *tiop, int in_fd, Buffer *cmd, char **env) { int len; Channel *c = NULL; debug2("%s: id %d", __func__, id); if ((c = channel_lookup(id)) == NULL) fatal("client_session2_setup: channel %d: unknown channel", id); packet_set_interactive(want_tty, options.ip_qos_interactive, options.ip_qos_bulk); if (want_tty) { struct winsize ws; /* Store window size in the packet. */ if (ioctl(in_fd, TIOCGWINSZ, &ws) < 0) memset(&ws, 0, sizeof(ws)); channel_request_start(id, "pty-req", 1); client_expect_confirm(id, "PTY allocation", 1); packet_put_cstring(term != NULL ? term : ""); packet_put_int((u_int)ws.ws_col); packet_put_int((u_int)ws.ws_row); packet_put_int((u_int)ws.ws_xpixel); packet_put_int((u_int)ws.ws_ypixel); if (tiop == NULL) tiop = get_saved_tio(); tty_make_modes(-1, tiop); packet_send(); /* XXX wait for reply */ c->client_tty = 1; } /* Transfer any environment variables from client to server */ if (options.num_send_env != 0 && env != NULL) { int i, j, matched; char *name, *val; debug("Sending environment."); for (i = 0; env[i] != NULL; i++) { /* Split */ name = xstrdup(env[i]); if ((val = strchr(name, '=')) == NULL) { xfree(name); continue; } *val++ = '\0'; matched = 0; for (j = 0; j < options.num_send_env; j++) { if (match_pattern(name, options.send_env[j])) { matched = 1; break; } } if (!matched) { debug3("Ignored env %s", name); xfree(name); continue; } debug("Sending env %s = %s", name, val); channel_request_start(id, "env", 0); packet_put_cstring(name); packet_put_cstring(val); packet_send(); xfree(name); } } len = buffer_len(cmd); if (len > 0) { if (len > 900) len = 900; if (want_subsystem) { debug("Sending subsystem: %.*s", len, (u_char*)buffer_ptr(cmd)); channel_request_start(id, "subsystem", 1); client_expect_confirm(id, "subsystem", 1); } else { debug("Sending command: %.*s", len, (u_char*)buffer_ptr(cmd)); channel_request_start(id, "exec", 1); client_expect_confirm(id, "exec", 1); } packet_put_string(buffer_ptr(cmd), buffer_len(cmd)); packet_send(); } else { channel_request_start(id, "shell", 1); client_expect_confirm(id, "shell", 1); packet_send(); } } static void client_init_dispatch_20(void) { dispatch_init(&dispatch_protocol_error); dispatch_set(SSH2_MSG_CHANNEL_CLOSE, &channel_input_oclose); dispatch_set(SSH2_MSG_CHANNEL_DATA, &channel_input_data); dispatch_set(SSH2_MSG_CHANNEL_EOF, &channel_input_ieof); dispatch_set(SSH2_MSG_CHANNEL_EXTENDED_DATA, &channel_input_extended_data); dispatch_set(SSH2_MSG_CHANNEL_OPEN, &client_input_channel_open); dispatch_set(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION, &channel_input_open_confirmation); dispatch_set(SSH2_MSG_CHANNEL_OPEN_FAILURE, &channel_input_open_failure); dispatch_set(SSH2_MSG_CHANNEL_REQUEST, &client_input_channel_req); dispatch_set(SSH2_MSG_CHANNEL_WINDOW_ADJUST, &channel_input_window_adjust); dispatch_set(SSH2_MSG_CHANNEL_SUCCESS, &channel_input_status_confirm); dispatch_set(SSH2_MSG_CHANNEL_FAILURE, &channel_input_status_confirm); dispatch_set(SSH2_MSG_GLOBAL_REQUEST, &client_input_global_request); /* rekeying */ dispatch_set(SSH2_MSG_KEXINIT, &kex_input_kexinit); /* global request reply messages */ dispatch_set(SSH2_MSG_REQUEST_FAILURE, &client_global_request_reply); dispatch_set(SSH2_MSG_REQUEST_SUCCESS, &client_global_request_reply); } static void client_init_dispatch_13(void) { dispatch_init(NULL); dispatch_set(SSH_MSG_CHANNEL_CLOSE, &channel_input_close); dispatch_set(SSH_MSG_CHANNEL_CLOSE_CONFIRMATION, &channel_input_close_confirmation); dispatch_set(SSH_MSG_CHANNEL_DATA, &channel_input_data); dispatch_set(SSH_MSG_CHANNEL_OPEN_CONFIRMATION, &channel_input_open_confirmation); dispatch_set(SSH_MSG_CHANNEL_OPEN_FAILURE, &channel_input_open_failure); dispatch_set(SSH_MSG_PORT_OPEN, &channel_input_port_open); dispatch_set(SSH_SMSG_EXITSTATUS, &client_input_exit_status); dispatch_set(SSH_SMSG_STDERR_DATA, &client_input_stderr_data); dispatch_set(SSH_SMSG_STDOUT_DATA, &client_input_stdout_data); dispatch_set(SSH_SMSG_AGENT_OPEN, options.forward_agent ? &client_input_agent_open : &deny_input_open); dispatch_set(SSH_SMSG_X11_OPEN, options.forward_x11 ? &x11_input_open : &deny_input_open); } static void client_init_dispatch_15(void) { client_init_dispatch_13(); dispatch_set(SSH_MSG_CHANNEL_CLOSE, &channel_input_ieof); dispatch_set(SSH_MSG_CHANNEL_CLOSE_CONFIRMATION, & channel_input_oclose); } static void client_init_dispatch(void) { if (compat20) client_init_dispatch_20(); else if (compat13) client_init_dispatch_13(); else client_init_dispatch_15(); } /* client specific fatal cleanup */ void cleanup_exit(int i) { leave_raw_mode(force_tty_flag); leave_non_blocking(); if (options.control_path != NULL && muxserver_sock != -1) unlink(options.control_path); ssh_kill_proxy_command(); _exit(i); } diff --git a/crypto/openssh/compat.c b/crypto/openssh/compat.c index df3541df70ef..d685f6dc9aef 100644 --- a/crypto/openssh/compat.c +++ b/crypto/openssh/compat.c @@ -1,237 +1,248 @@ /* $OpenBSD: compat.c,v 1.78 2008/09/11 14:22:37 markus Exp $ */ +/* $FreeBSD$ */ /* * Copyright (c) 1999, 2000, 2001, 2002 Markus Friedl. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "includes.h" #include #include #include #include #include "xmalloc.h" #include "buffer.h" #include "packet.h" #include "compat.h" #include "log.h" #include "match.h" int compat13 = 0; int compat20 = 0; int datafellows = 0; void enable_compat20(void) { debug("Enabling compatibility mode for protocol 2.0"); compat20 = 1; } void enable_compat13(void) { debug("Enabling compatibility mode for protocol 1.3"); compat13 = 1; } /* datafellows bug compatibility */ void compat_datafellows(const char *version) { int i; static struct { char *pat; int bugs; } check[] = { { "OpenSSH-2.0*," "OpenSSH-2.1*," "OpenSSH_2.1*," "OpenSSH_2.2*", SSH_OLD_SESSIONID|SSH_BUG_BANNER| SSH_OLD_DHGEX|SSH_BUG_NOREKEY| SSH_BUG_EXTEOF|SSH_OLD_FORWARD_ADDR}, { "OpenSSH_2.3.0*", SSH_BUG_BANNER|SSH_BUG_BIGENDIANAES| SSH_OLD_DHGEX|SSH_BUG_NOREKEY| SSH_BUG_EXTEOF|SSH_OLD_FORWARD_ADDR}, { "OpenSSH_2.3.*", SSH_BUG_BIGENDIANAES|SSH_OLD_DHGEX| SSH_BUG_NOREKEY|SSH_BUG_EXTEOF| SSH_OLD_FORWARD_ADDR}, { "OpenSSH_2.5.0p1*," "OpenSSH_2.5.1p1*", SSH_BUG_BIGENDIANAES|SSH_OLD_DHGEX| SSH_BUG_NOREKEY|SSH_BUG_EXTEOF| SSH_OLD_FORWARD_ADDR}, { "OpenSSH_2.5.0*," "OpenSSH_2.5.1*," "OpenSSH_2.5.2*", SSH_OLD_DHGEX|SSH_BUG_NOREKEY| SSH_BUG_EXTEOF|SSH_OLD_FORWARD_ADDR}, { "OpenSSH_2.5.3*", SSH_BUG_NOREKEY|SSH_BUG_EXTEOF| SSH_OLD_FORWARD_ADDR}, { "OpenSSH_2.*," "OpenSSH_3.0*," "OpenSSH_3.1*", SSH_BUG_EXTEOF|SSH_OLD_FORWARD_ADDR}, { "OpenSSH_3.*", SSH_OLD_FORWARD_ADDR }, { "Sun_SSH_1.0*", SSH_BUG_NOREKEY|SSH_BUG_EXTEOF}, { "OpenSSH_4*", 0 }, { "OpenSSH*", SSH_NEW_OPENSSH }, { "*MindTerm*", 0 }, { "2.1.0*", SSH_BUG_SIGBLOB|SSH_BUG_HMAC| SSH_OLD_SESSIONID|SSH_BUG_DEBUG| SSH_BUG_RSASIGMD5|SSH_BUG_HBSERVICE| SSH_BUG_FIRSTKEX }, { "2.1 *", SSH_BUG_SIGBLOB|SSH_BUG_HMAC| SSH_OLD_SESSIONID|SSH_BUG_DEBUG| SSH_BUG_RSASIGMD5|SSH_BUG_HBSERVICE| SSH_BUG_FIRSTKEX }, { "2.0.13*," "2.0.14*," "2.0.15*," "2.0.16*," "2.0.17*," "2.0.18*," "2.0.19*", SSH_BUG_SIGBLOB|SSH_BUG_HMAC| SSH_OLD_SESSIONID|SSH_BUG_DEBUG| SSH_BUG_PKSERVICE|SSH_BUG_X11FWD| SSH_BUG_PKOK|SSH_BUG_RSASIGMD5| SSH_BUG_HBSERVICE|SSH_BUG_OPENFAILURE| SSH_BUG_DUMMYCHAN|SSH_BUG_FIRSTKEX }, { "2.0.11*," "2.0.12*", SSH_BUG_SIGBLOB|SSH_BUG_HMAC| SSH_OLD_SESSIONID|SSH_BUG_DEBUG| SSH_BUG_PKSERVICE|SSH_BUG_X11FWD| SSH_BUG_PKAUTH|SSH_BUG_PKOK| SSH_BUG_RSASIGMD5|SSH_BUG_OPENFAILURE| SSH_BUG_DUMMYCHAN|SSH_BUG_FIRSTKEX }, { "2.0.*", SSH_BUG_SIGBLOB|SSH_BUG_HMAC| SSH_OLD_SESSIONID|SSH_BUG_DEBUG| SSH_BUG_PKSERVICE|SSH_BUG_X11FWD| SSH_BUG_PKAUTH|SSH_BUG_PKOK| SSH_BUG_RSASIGMD5|SSH_BUG_OPENFAILURE| SSH_BUG_DERIVEKEY|SSH_BUG_DUMMYCHAN| SSH_BUG_FIRSTKEX }, { "2.2.0*," "2.3.0*", SSH_BUG_HMAC|SSH_BUG_DEBUG| SSH_BUG_RSASIGMD5|SSH_BUG_FIRSTKEX }, { "2.3.*", SSH_BUG_DEBUG|SSH_BUG_RSASIGMD5| SSH_BUG_FIRSTKEX }, { "2.4", SSH_OLD_SESSIONID }, /* Van Dyke */ { "2.*", SSH_BUG_DEBUG|SSH_BUG_FIRSTKEX| SSH_BUG_RFWD_ADDR }, { "3.0.*", SSH_BUG_DEBUG }, { "3.0 SecureCRT*", SSH_OLD_SESSIONID }, { "1.7 SecureFX*", SSH_OLD_SESSIONID }, { "1.2.18*," "1.2.19*," "1.2.20*," "1.2.21*," "1.2.22*", SSH_BUG_IGNOREMSG }, { "1.3.2*", /* F-Secure */ SSH_BUG_IGNOREMSG }, { "*SSH Compatible Server*", /* Netscreen */ SSH_BUG_PASSWORDPAD }, { "*OSU_0*," "OSU_1.0*," "OSU_1.1*," "OSU_1.2*," "OSU_1.3*," "OSU_1.4*," "OSU_1.5alpha1*," "OSU_1.5alpha2*," "OSU_1.5alpha3*", SSH_BUG_PASSWORDPAD }, { "*SSH_Version_Mapper*", SSH_BUG_SCANNER }, { "Probe-*", SSH_BUG_PROBE }, { NULL, 0 } }; /* process table, return first match */ for (i = 0; check[i].pat; i++) { if (match_pattern_list(version, check[i].pat, strlen(check[i].pat), 0) == 1) { debug("match: %s pat %s", version, check[i].pat); datafellows = check[i].bugs; + /* + * Check to see if the remote side is OpenSSH and not + * HPN. It is utterly strange to check it from the + * version string and expose the option that way. + */ + if (strstr(version,"OpenSSH") != NULL && + strstr(version,"hpn") == NULL) { + datafellows |= SSH_BUG_LARGEWINDOW; + debug("Remote is not HPN-aware"); + } return; } } debug("no match: %s", version); } #define SEP "," int proto_spec(const char *spec) { char *s, *p, *q; int ret = SSH_PROTO_UNKNOWN; if (spec == NULL) return ret; q = s = xstrdup(spec); for ((p = strsep(&q, SEP)); p && *p != '\0'; (p = strsep(&q, SEP))) { switch (atoi(p)) { case 1: if (ret == SSH_PROTO_UNKNOWN) ret |= SSH_PROTO_1_PREFERRED; ret |= SSH_PROTO_1; break; case 2: ret |= SSH_PROTO_2; break; default: logit("ignoring bad proto spec: '%s'.", p); break; } } xfree(s); return ret; } char * compat_cipher_proposal(char *cipher_prop) { Buffer b; char *orig_prop, *fix_ciphers; char *cp, *tmp; if (!(datafellows & SSH_BUG_BIGENDIANAES)) return(cipher_prop); buffer_init(&b); tmp = orig_prop = xstrdup(cipher_prop); while ((cp = strsep(&tmp, ",")) != NULL) { if (strncmp(cp, "aes", 3) != 0) { if (buffer_len(&b) > 0) buffer_append(&b, ",", 1); buffer_append(&b, cp, strlen(cp)); } } buffer_append(&b, "\0", 1); fix_ciphers = xstrdup(buffer_ptr(&b)); buffer_free(&b); xfree(orig_prop); debug2("Original cipher proposal: %s", cipher_prop); debug2("Compat cipher proposal: %s", fix_ciphers); if (!*fix_ciphers) fatal("No available ciphers found."); return(fix_ciphers); } diff --git a/crypto/openssh/compat.h b/crypto/openssh/compat.h index 16cf282a7aec..b8405267fdeb 100644 --- a/crypto/openssh/compat.h +++ b/crypto/openssh/compat.h @@ -1,71 +1,73 @@ /* $OpenBSD: compat.h,v 1.42 2008/09/11 14:22:37 markus Exp $ */ +/* $FReeBSD$ */ /* * Copyright (c) 1999, 2000, 2001 Markus Friedl. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef COMPAT_H #define COMPAT_H #define SSH_PROTO_UNKNOWN 0x00 #define SSH_PROTO_1 0x01 #define SSH_PROTO_1_PREFERRED 0x02 #define SSH_PROTO_2 0x04 #define SSH_BUG_SIGBLOB 0x00000001 #define SSH_BUG_PKSERVICE 0x00000002 #define SSH_BUG_HMAC 0x00000004 #define SSH_BUG_X11FWD 0x00000008 #define SSH_OLD_SESSIONID 0x00000010 #define SSH_BUG_PKAUTH 0x00000020 #define SSH_BUG_DEBUG 0x00000040 #define SSH_BUG_BANNER 0x00000080 #define SSH_BUG_IGNOREMSG 0x00000100 #define SSH_BUG_PKOK 0x00000200 #define SSH_BUG_PASSWORDPAD 0x00000400 #define SSH_BUG_SCANNER 0x00000800 #define SSH_BUG_BIGENDIANAES 0x00001000 #define SSH_BUG_RSASIGMD5 0x00002000 #define SSH_OLD_DHGEX 0x00004000 #define SSH_BUG_NOREKEY 0x00008000 #define SSH_BUG_HBSERVICE 0x00010000 #define SSH_BUG_OPENFAILURE 0x00020000 #define SSH_BUG_DERIVEKEY 0x00040000 #define SSH_BUG_DUMMYCHAN 0x00100000 #define SSH_BUG_EXTEOF 0x00200000 #define SSH_BUG_PROBE 0x00400000 #define SSH_BUG_FIRSTKEX 0x00800000 #define SSH_OLD_FORWARD_ADDR 0x01000000 #define SSH_BUG_RFWD_ADDR 0x02000000 #define SSH_NEW_OPENSSH 0x04000000 +#define SSH_BUG_LARGEWINDOW 0x08000000 void enable_compat13(void); void enable_compat20(void); void compat_datafellows(const char *); int proto_spec(const char *); char *compat_cipher_proposal(char *); extern int compat13; extern int compat20; extern int datafellows; #endif diff --git a/crypto/openssh/kex.c b/crypto/openssh/kex.c index c65e28f94dec..ca3c4206a106 100644 --- a/crypto/openssh/kex.c +++ b/crypto/openssh/kex.c @@ -1,610 +1,634 @@ /* $OpenBSD: kex.c,v 1.86 2010/09/22 05:01:29 djm Exp $ */ +/* $FreeBSD$ */ /* * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "includes.h" #include #include #include #include #include #include #include #include "xmalloc.h" #include "ssh2.h" #include "buffer.h" #include "packet.h" #include "compat.h" #include "cipher.h" #include "key.h" #include "kex.h" #include "log.h" #include "mac.h" #include "match.h" #include "dispatch.h" #include "monitor.h" #include "roaming.h" #if OPENSSL_VERSION_NUMBER >= 0x00907000L # if defined(HAVE_EVP_SHA256) # define evp_ssh_sha256 EVP_sha256 # else extern const EVP_MD *evp_ssh_sha256(void); # endif #endif /* prototype */ static void kex_kexinit_finish(Kex *); static void kex_choose_conf(Kex *); /* Validate KEX method name list */ int kex_names_valid(const char *names) { char *s, *cp, *p; if (names == NULL || strcmp(names, "") == 0) return 0; s = cp = xstrdup(names); for ((p = strsep(&cp, ",")); p && *p != '\0'; (p = strsep(&cp, ","))) { if (strcmp(p, KEX_DHGEX_SHA256) != 0 && strcmp(p, KEX_DHGEX_SHA1) != 0 && strcmp(p, KEX_DH14) != 0 && strcmp(p, KEX_DH1) != 0 && (strncmp(p, KEX_ECDH_SHA2_STEM, sizeof(KEX_ECDH_SHA2_STEM) - 1) != 0 || kex_ecdh_name_to_nid(p) == -1)) { error("Unsupported KEX algorithm \"%.100s\"", p); xfree(s); return 0; } } debug3("kex names ok: [%s]", names); xfree(s); return 1; } -/* put algorithm proposal into buffer */ +/* Put algorithm proposal into buffer. */ +#ifndef NONE_CIPHER_ENABLED static void +#else +/* Also used in sshconnect2.c. */ +void +#endif kex_prop2buf(Buffer *b, char *proposal[PROPOSAL_MAX]) { u_int i; buffer_clear(b); /* * add a dummy cookie, the cookie will be overwritten by * kex_send_kexinit(), each time a kexinit is set */ for (i = 0; i < KEX_COOKIE_LEN; i++) buffer_put_char(b, 0); for (i = 0; i < PROPOSAL_MAX; i++) buffer_put_cstring(b, proposal[i]); buffer_put_char(b, 0); /* first_kex_packet_follows */ buffer_put_int(b, 0); /* uint32 reserved */ } /* parse buffer and return algorithm proposal */ static char ** kex_buf2prop(Buffer *raw, int *first_kex_follows) { Buffer b; u_int i; char **proposal; proposal = xcalloc(PROPOSAL_MAX, sizeof(char *)); buffer_init(&b); buffer_append(&b, buffer_ptr(raw), buffer_len(raw)); /* skip cookie */ for (i = 0; i < KEX_COOKIE_LEN; i++) buffer_get_char(&b); /* extract kex init proposal strings */ for (i = 0; i < PROPOSAL_MAX; i++) { proposal[i] = buffer_get_cstring(&b,NULL); debug2("kex_parse_kexinit: %s", proposal[i]); } /* first kex follows / reserved */ i = buffer_get_char(&b); if (first_kex_follows != NULL) *first_kex_follows = i; debug2("kex_parse_kexinit: first_kex_follows %d ", i); i = buffer_get_int(&b); debug2("kex_parse_kexinit: reserved %u ", i); buffer_free(&b); return proposal; } static void kex_prop_free(char **proposal) { u_int i; for (i = 0; i < PROPOSAL_MAX; i++) xfree(proposal[i]); xfree(proposal); } /* ARGSUSED */ static void kex_protocol_error(int type, u_int32_t seq, void *ctxt) { error("Hm, kex protocol error: type %d seq %u", type, seq); } static void kex_reset_dispatch(void) { dispatch_range(SSH2_MSG_TRANSPORT_MIN, SSH2_MSG_TRANSPORT_MAX, &kex_protocol_error); dispatch_set(SSH2_MSG_KEXINIT, &kex_input_kexinit); } void kex_finish(Kex *kex) { kex_reset_dispatch(); packet_start(SSH2_MSG_NEWKEYS); packet_send(); /* packet_write_wait(); */ debug("SSH2_MSG_NEWKEYS sent"); debug("expecting SSH2_MSG_NEWKEYS"); packet_read_expect(SSH2_MSG_NEWKEYS); packet_check_eom(); debug("SSH2_MSG_NEWKEYS received"); kex->done = 1; buffer_clear(&kex->peer); /* buffer_clear(&kex->my); */ kex->flags &= ~KEX_INIT_SENT; xfree(kex->name); kex->name = NULL; } void kex_send_kexinit(Kex *kex) { u_int32_t rnd = 0; u_char *cookie; u_int i; if (kex == NULL) { error("kex_send_kexinit: no kex, cannot rekey"); return; } if (kex->flags & KEX_INIT_SENT) { debug("KEX_INIT_SENT"); return; } kex->done = 0; /* generate a random cookie */ if (buffer_len(&kex->my) < KEX_COOKIE_LEN) fatal("kex_send_kexinit: kex proposal too short"); cookie = buffer_ptr(&kex->my); for (i = 0; i < KEX_COOKIE_LEN; i++) { if (i % 4 == 0) rnd = arc4random(); cookie[i] = rnd; rnd >>= 8; } packet_start(SSH2_MSG_KEXINIT); packet_put_raw(buffer_ptr(&kex->my), buffer_len(&kex->my)); packet_send(); debug("SSH2_MSG_KEXINIT sent"); kex->flags |= KEX_INIT_SENT; } /* ARGSUSED */ void kex_input_kexinit(int type, u_int32_t seq, void *ctxt) { char *ptr; u_int i, dlen; Kex *kex = (Kex *)ctxt; debug("SSH2_MSG_KEXINIT received"); if (kex == NULL) fatal("kex_input_kexinit: no kex, cannot rekey"); ptr = packet_get_raw(&dlen); buffer_append(&kex->peer, ptr, dlen); /* discard packet */ for (i = 0; i < KEX_COOKIE_LEN; i++) packet_get_char(); for (i = 0; i < PROPOSAL_MAX; i++) xfree(packet_get_string(NULL)); (void) packet_get_char(); (void) packet_get_int(); packet_check_eom(); kex_kexinit_finish(kex); } Kex * kex_setup(char *proposal[PROPOSAL_MAX]) { Kex *kex; kex = xcalloc(1, sizeof(*kex)); buffer_init(&kex->peer); buffer_init(&kex->my); kex_prop2buf(&kex->my, proposal); kex->done = 0; kex_send_kexinit(kex); /* we start */ kex_reset_dispatch(); return kex; } static void kex_kexinit_finish(Kex *kex) { if (!(kex->flags & KEX_INIT_SENT)) kex_send_kexinit(kex); kex_choose_conf(kex); if (kex->kex_type >= 0 && kex->kex_type < KEX_MAX && kex->kex[kex->kex_type] != NULL) { (kex->kex[kex->kex_type])(kex); } else { fatal("Unsupported key exchange %d", kex->kex_type); } } static void choose_enc(Enc *enc, char *client, char *server) { char *name = match_list(client, server, NULL); if (name == NULL) fatal("no matching cipher found: client %s server %s", client, server); if ((enc->cipher = cipher_by_name(name)) == NULL) fatal("matching cipher is not supported: %s", name); enc->name = name; enc->enabled = 0; enc->iv = NULL; enc->key = NULL; enc->key_len = cipher_keylen(enc->cipher); enc->block_size = cipher_blocksize(enc->cipher); } static void choose_mac(Mac *mac, char *client, char *server) { char *name = match_list(client, server, NULL); if (name == NULL) fatal("no matching mac found: client %s server %s", client, server); if (mac_setup(mac, name) < 0) fatal("unsupported mac %s", name); /* truncate the key */ if (datafellows & SSH_BUG_HMAC) mac->key_len = 16; mac->name = name; mac->key = NULL; mac->enabled = 0; } static void choose_comp(Comp *comp, char *client, char *server) { char *name = match_list(client, server, NULL); if (name == NULL) fatal("no matching comp found: client %s server %s", client, server); if (strcmp(name, "zlib@openssh.com") == 0) { comp->type = COMP_DELAYED; } else if (strcmp(name, "zlib") == 0) { comp->type = COMP_ZLIB; } else if (strcmp(name, "none") == 0) { comp->type = COMP_NONE; } else { fatal("unsupported comp %s", name); } comp->name = name; } static void choose_kex(Kex *k, char *client, char *server) { k->name = match_list(client, server, NULL); if (k->name == NULL) fatal("Unable to negotiate a key exchange method"); if (strcmp(k->name, KEX_DH1) == 0) { k->kex_type = KEX_DH_GRP1_SHA1; k->evp_md = EVP_sha1(); } else if (strcmp(k->name, KEX_DH14) == 0) { k->kex_type = KEX_DH_GRP14_SHA1; k->evp_md = EVP_sha1(); } else if (strcmp(k->name, KEX_DHGEX_SHA1) == 0) { k->kex_type = KEX_DH_GEX_SHA1; k->evp_md = EVP_sha1(); #if OPENSSL_VERSION_NUMBER >= 0x00907000L } else if (strcmp(k->name, KEX_DHGEX_SHA256) == 0) { k->kex_type = KEX_DH_GEX_SHA256; k->evp_md = evp_ssh_sha256(); } else if (strncmp(k->name, KEX_ECDH_SHA2_STEM, sizeof(KEX_ECDH_SHA2_STEM) - 1) == 0) { k->kex_type = KEX_ECDH_SHA2; k->evp_md = kex_ecdh_name_to_evpmd(k->name); #endif } else fatal("bad kex alg %s", k->name); } static void choose_hostkeyalg(Kex *k, char *client, char *server) { char *hostkeyalg = match_list(client, server, NULL); if (hostkeyalg == NULL) fatal("no hostkey alg"); k->hostkey_type = key_type_from_name(hostkeyalg); if (k->hostkey_type == KEY_UNSPEC) fatal("bad hostkey alg '%s'", hostkeyalg); xfree(hostkeyalg); } static int proposals_match(char *my[PROPOSAL_MAX], char *peer[PROPOSAL_MAX]) { static int check[] = { PROPOSAL_KEX_ALGS, PROPOSAL_SERVER_HOST_KEY_ALGS, -1 }; int *idx; char *p; for (idx = &check[0]; *idx != -1; idx++) { if ((p = strchr(my[*idx], ',')) != NULL) *p = '\0'; if ((p = strchr(peer[*idx], ',')) != NULL) *p = '\0'; if (strcmp(my[*idx], peer[*idx]) != 0) { debug2("proposal mismatch: my %s peer %s", my[*idx], peer[*idx]); return (0); } } debug2("proposals match"); return (1); } static void kex_choose_conf(Kex *kex) { Newkeys *newkeys; char **my, **peer; char **cprop, **sprop; int nenc, nmac, ncomp; u_int mode, ctos, need; int first_kex_follows, type; +#ifdef NONE_CIPHER_ENABLED + int auth_flag; +#endif my = kex_buf2prop(&kex->my, NULL); peer = kex_buf2prop(&kex->peer, &first_kex_follows); if (kex->server) { cprop=peer; sprop=my; } else { cprop=my; sprop=peer; } /* Check whether server offers roaming */ if (!kex->server) { char *roaming; roaming = match_list(KEX_RESUME, peer[PROPOSAL_KEX_ALGS], NULL); if (roaming) { kex->roaming = 1; xfree(roaming); } } /* Algorithm Negotiation */ +#ifdef NONE_CIPHER_ENABLED + auth_flag = packet_get_authentication_state(); + debug ("AUTH STATE is %d", auth_flag); +#endif for (mode = 0; mode < MODE_MAX; mode++) { newkeys = xcalloc(1, sizeof(*newkeys)); kex->newkeys[mode] = newkeys; ctos = (!kex->server && mode == MODE_OUT) || (kex->server && mode == MODE_IN); nenc = ctos ? PROPOSAL_ENC_ALGS_CTOS : PROPOSAL_ENC_ALGS_STOC; nmac = ctos ? PROPOSAL_MAC_ALGS_CTOS : PROPOSAL_MAC_ALGS_STOC; ncomp = ctos ? PROPOSAL_COMP_ALGS_CTOS : PROPOSAL_COMP_ALGS_STOC; choose_enc (&newkeys->enc, cprop[nenc], sprop[nenc]); choose_mac (&newkeys->mac, cprop[nmac], sprop[nmac]); choose_comp(&newkeys->comp, cprop[ncomp], sprop[ncomp]); +#ifdef NONE_CIPHER_ENABLED + debug("REQUESTED ENC.NAME is '%s'", newkeys->enc.name); + if (strcmp(newkeys->enc.name, "none") == 0) { + debug("Requesting NONE. Authflag is %d", auth_flag); + if (auth_flag == 1) + debug("None requested post authentication."); + else + fatal("Pre-authentication none cipher requests " + "are not allowed."); + } +#endif debug("kex: %s %s %s %s", ctos ? "client->server" : "server->client", newkeys->enc.name, newkeys->mac.name, newkeys->comp.name); } choose_kex(kex, cprop[PROPOSAL_KEX_ALGS], sprop[PROPOSAL_KEX_ALGS]); choose_hostkeyalg(kex, cprop[PROPOSAL_SERVER_HOST_KEY_ALGS], sprop[PROPOSAL_SERVER_HOST_KEY_ALGS]); need = 0; for (mode = 0; mode < MODE_MAX; mode++) { newkeys = kex->newkeys[mode]; if (need < newkeys->enc.key_len) need = newkeys->enc.key_len; if (need < newkeys->enc.block_size) need = newkeys->enc.block_size; if (need < newkeys->mac.key_len) need = newkeys->mac.key_len; } /* XXX need runden? */ kex->we_need = need; /* ignore the next message if the proposals do not match */ if (first_kex_follows && !proposals_match(my, peer) && !(datafellows & SSH_BUG_FIRSTKEX)) { type = packet_read(); debug2("skipping next packet (type %u)", type); } kex_prop_free(my); kex_prop_free(peer); } static u_char * derive_key(Kex *kex, int id, u_int need, u_char *hash, u_int hashlen, BIGNUM *shared_secret) { Buffer b; EVP_MD_CTX md; char c = id; u_int have; int mdsz; u_char *digest; if ((mdsz = EVP_MD_size(kex->evp_md)) <= 0) fatal("bad kex md size %d", mdsz); digest = xmalloc(roundup(need, mdsz)); buffer_init(&b); buffer_put_bignum2(&b, shared_secret); /* K1 = HASH(K || H || "A" || session_id) */ EVP_DigestInit(&md, kex->evp_md); if (!(datafellows & SSH_BUG_DERIVEKEY)) EVP_DigestUpdate(&md, buffer_ptr(&b), buffer_len(&b)); EVP_DigestUpdate(&md, hash, hashlen); EVP_DigestUpdate(&md, &c, 1); EVP_DigestUpdate(&md, kex->session_id, kex->session_id_len); EVP_DigestFinal(&md, digest, NULL); /* * expand key: * Kn = HASH(K || H || K1 || K2 || ... || Kn-1) * Key = K1 || K2 || ... || Kn */ for (have = mdsz; need > have; have += mdsz) { EVP_DigestInit(&md, kex->evp_md); if (!(datafellows & SSH_BUG_DERIVEKEY)) EVP_DigestUpdate(&md, buffer_ptr(&b), buffer_len(&b)); EVP_DigestUpdate(&md, hash, hashlen); EVP_DigestUpdate(&md, digest, have); EVP_DigestFinal(&md, digest + have, NULL); } buffer_free(&b); #ifdef DEBUG_KEX fprintf(stderr, "key '%c'== ", c); dump_digest("key", digest, need); #endif return digest; } Newkeys *current_keys[MODE_MAX]; #define NKEYS 6 void kex_derive_keys(Kex *kex, u_char *hash, u_int hashlen, BIGNUM *shared_secret) { u_char *keys[NKEYS]; u_int i, mode, ctos; for (i = 0; i < NKEYS; i++) { keys[i] = derive_key(kex, 'A'+i, kex->we_need, hash, hashlen, shared_secret); } debug2("kex_derive_keys"); for (mode = 0; mode < MODE_MAX; mode++) { current_keys[mode] = kex->newkeys[mode]; kex->newkeys[mode] = NULL; ctos = (!kex->server && mode == MODE_OUT) || (kex->server && mode == MODE_IN); current_keys[mode]->enc.iv = keys[ctos ? 0 : 1]; current_keys[mode]->enc.key = keys[ctos ? 2 : 3]; current_keys[mode]->mac.key = keys[ctos ? 4 : 5]; } } Newkeys * kex_get_newkeys(int mode) { Newkeys *ret; ret = current_keys[mode]; current_keys[mode] = NULL; return ret; } void derive_ssh1_session_id(BIGNUM *host_modulus, BIGNUM *server_modulus, u_int8_t cookie[8], u_int8_t id[16]) { const EVP_MD *evp_md = EVP_md5(); EVP_MD_CTX md; u_int8_t nbuf[2048], obuf[EVP_MAX_MD_SIZE]; int len; EVP_DigestInit(&md, evp_md); len = BN_num_bytes(host_modulus); if (len < (512 / 8) || (u_int)len > sizeof(nbuf)) fatal("%s: bad host modulus (len %d)", __func__, len); BN_bn2bin(host_modulus, nbuf); EVP_DigestUpdate(&md, nbuf, len); len = BN_num_bytes(server_modulus); if (len < (512 / 8) || (u_int)len > sizeof(nbuf)) fatal("%s: bad server modulus (len %d)", __func__, len); BN_bn2bin(server_modulus, nbuf); EVP_DigestUpdate(&md, nbuf, len); EVP_DigestUpdate(&md, cookie, 8); EVP_DigestFinal(&md, obuf, NULL); memcpy(id, obuf, 16); memset(nbuf, 0, sizeof(nbuf)); memset(obuf, 0, sizeof(obuf)); memset(&md, 0, sizeof(md)); } #if defined(DEBUG_KEX) || defined(DEBUG_KEXDH) || defined(DEBUG_KEXECDH) void dump_digest(char *msg, u_char *digest, int len) { int i; fprintf(stderr, "%s\n", msg); for (i = 0; i < len; i++) { fprintf(stderr, "%02x", digest[i]); if (i%32 == 31) fprintf(stderr, "\n"); else if (i%8 == 7) fprintf(stderr, " "); } fprintf(stderr, "\n"); } #endif diff --git a/crypto/openssh/kex.h b/crypto/openssh/kex.h index 7373d3c789f4..fc5bdf6e03ae 100644 --- a/crypto/openssh/kex.h +++ b/crypto/openssh/kex.h @@ -1,185 +1,190 @@ /* $OpenBSD: kex.h,v 1.52 2010/09/22 05:01:29 djm Exp $ */ +/* $FreeBSD$ */ /* * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef KEX_H #define KEX_H #include #include #include #ifdef OPENSSL_HAS_ECC #include #endif #define KEX_COOKIE_LEN 16 #define KEX_DH1 "diffie-hellman-group1-sha1" #define KEX_DH14 "diffie-hellman-group14-sha1" #define KEX_DHGEX_SHA1 "diffie-hellman-group-exchange-sha1" #define KEX_DHGEX_SHA256 "diffie-hellman-group-exchange-sha256" #define KEX_RESUME "resume@appgate.com" /* The following represents the family of ECDH methods */ #define KEX_ECDH_SHA2_STEM "ecdh-sha2-" #define COMP_NONE 0 #define COMP_ZLIB 1 #define COMP_DELAYED 2 enum kex_init_proposals { PROPOSAL_KEX_ALGS, PROPOSAL_SERVER_HOST_KEY_ALGS, PROPOSAL_ENC_ALGS_CTOS, PROPOSAL_ENC_ALGS_STOC, PROPOSAL_MAC_ALGS_CTOS, PROPOSAL_MAC_ALGS_STOC, PROPOSAL_COMP_ALGS_CTOS, PROPOSAL_COMP_ALGS_STOC, PROPOSAL_LANG_CTOS, PROPOSAL_LANG_STOC, PROPOSAL_MAX }; enum kex_modes { MODE_IN, MODE_OUT, MODE_MAX }; enum kex_exchange { KEX_DH_GRP1_SHA1, KEX_DH_GRP14_SHA1, KEX_DH_GEX_SHA1, KEX_DH_GEX_SHA256, KEX_ECDH_SHA2, KEX_MAX }; #define KEX_INIT_SENT 0x0001 typedef struct Kex Kex; typedef struct Mac Mac; typedef struct Comp Comp; typedef struct Enc Enc; typedef struct Newkeys Newkeys; struct Enc { char *name; Cipher *cipher; int enabled; u_int key_len; u_int block_size; u_char *key; u_char *iv; }; struct Mac { char *name; int enabled; u_int mac_len; u_char *key; u_int key_len; int type; const EVP_MD *evp_md; HMAC_CTX evp_ctx; struct umac_ctx *umac_ctx; }; struct Comp { int type; int enabled; char *name; }; struct Newkeys { Enc enc; Mac mac; Comp comp; }; struct Kex { u_char *session_id; u_int session_id_len; Newkeys *newkeys[MODE_MAX]; u_int we_need; int server; char *name; int hostkey_type; int kex_type; int roaming; Buffer my; Buffer peer; sig_atomic_t done; int flags; const EVP_MD *evp_md; char *client_version_string; char *server_version_string; int (*verify_host_key)(Key *); Key *(*load_host_public_key)(int); Key *(*load_host_private_key)(int); int (*host_key_index)(Key *); void (*kex[KEX_MAX])(Kex *); }; int kex_names_valid(const char *); +#ifdef NONE_CIPHER_ENABLED +void kex_prop2buf(Buffer *, char *[PROPOSAL_MAX]); +#endif + Kex *kex_setup(char *[PROPOSAL_MAX]); void kex_finish(Kex *); void kex_send_kexinit(Kex *); void kex_input_kexinit(int, u_int32_t, void *); void kex_derive_keys(Kex *, u_char *, u_int, BIGNUM *); Newkeys *kex_get_newkeys(int); void kexdh_client(Kex *); void kexdh_server(Kex *); void kexgex_client(Kex *); void kexgex_server(Kex *); void kexecdh_client(Kex *); void kexecdh_server(Kex *); void kex_dh_hash(char *, char *, char *, int, char *, int, u_char *, int, BIGNUM *, BIGNUM *, BIGNUM *, u_char **, u_int *); void kexgex_hash(const EVP_MD *, char *, char *, char *, int, char *, int, u_char *, int, int, int, int, BIGNUM *, BIGNUM *, BIGNUM *, BIGNUM *, BIGNUM *, u_char **, u_int *); #ifdef OPENSSL_HAS_ECC void kex_ecdh_hash(const EVP_MD *, const EC_GROUP *, char *, char *, char *, int, char *, int, u_char *, int, const EC_POINT *, const EC_POINT *, const BIGNUM *, u_char **, u_int *); int kex_ecdh_name_to_nid(const char *); const EVP_MD *kex_ecdh_name_to_evpmd(const char *); #else # define kex_ecdh_name_to_nid(x) (-1) # define kex_ecdh_name_to_evpmd(x) (NULL) #endif void derive_ssh1_session_id(BIGNUM *, BIGNUM *, u_int8_t[8], u_int8_t[16]); #if defined(DEBUG_KEX) || defined(DEBUG_KEXDH) || defined(DEBUG_KEXECDH) void dump_digest(char *, u_char *, int); #endif #endif diff --git a/crypto/openssh/misc.c b/crypto/openssh/misc.c index 919b04e6b7a9..1fc3ae74cb85 100644 --- a/crypto/openssh/misc.c +++ b/crypto/openssh/misc.c @@ -1,998 +1,1030 @@ /* $OpenBSD: misc.c,v 1.84 2010/11/21 01:01:13 djm Exp $ */ +/* $FreeBSD$ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. * Copyright (c) 2005,2006 Damien Miller. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "includes.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_PATHS_H # include #include #endif #ifdef SSH_TUN_OPENBSD #include #endif #include "xmalloc.h" #include "misc.h" #include "log.h" #include "ssh.h" /* remove newline at end of string */ char * chop(char *s) { char *t = s; while (*t) { if (*t == '\n' || *t == '\r') { *t = '\0'; return s; } t++; } return s; } /* set/unset filedescriptor to non-blocking */ int set_nonblock(int fd) { int val; val = fcntl(fd, F_GETFL, 0); if (val < 0) { error("fcntl(%d, F_GETFL, 0): %s", fd, strerror(errno)); return (-1); } if (val & O_NONBLOCK) { debug3("fd %d is O_NONBLOCK", fd); return (0); } debug2("fd %d setting O_NONBLOCK", fd); val |= O_NONBLOCK; if (fcntl(fd, F_SETFL, val) == -1) { debug("fcntl(%d, F_SETFL, O_NONBLOCK): %s", fd, strerror(errno)); return (-1); } return (0); } int unset_nonblock(int fd) { int val; val = fcntl(fd, F_GETFL, 0); if (val < 0) { error("fcntl(%d, F_GETFL, 0): %s", fd, strerror(errno)); return (-1); } if (!(val & O_NONBLOCK)) { debug3("fd %d is not O_NONBLOCK", fd); return (0); } debug("fd %d clearing O_NONBLOCK", fd); val &= ~O_NONBLOCK; if (fcntl(fd, F_SETFL, val) == -1) { debug("fcntl(%d, F_SETFL, ~O_NONBLOCK): %s", fd, strerror(errno)); return (-1); } return (0); } const char * ssh_gai_strerror(int gaierr) { if (gaierr == EAI_SYSTEM) return strerror(errno); return gai_strerror(gaierr); } /* disable nagle on socket */ void set_nodelay(int fd) { int opt; socklen_t optlen; optlen = sizeof opt; if (getsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &opt, &optlen) == -1) { debug("getsockopt TCP_NODELAY: %.100s", strerror(errno)); return; } if (opt == 1) { debug2("fd %d is TCP_NODELAY", fd); return; } opt = 1; debug2("fd %d setting TCP_NODELAY", fd); if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof opt) == -1) error("setsockopt TCP_NODELAY: %.100s", strerror(errno)); } /* Characters considered whitespace in strsep calls. */ #define WHITESPACE " \t\r\n" #define QUOTE "\"" /* return next token in configuration line */ char * strdelim(char **s) { char *old; int wspace = 0; if (*s == NULL) return NULL; old = *s; *s = strpbrk(*s, WHITESPACE QUOTE "="); if (*s == NULL) return (old); if (*s[0] == '\"') { memmove(*s, *s + 1, strlen(*s)); /* move nul too */ /* Find matching quote */ if ((*s = strpbrk(*s, QUOTE)) == NULL) { return (NULL); /* no matching quote */ } else { *s[0] = '\0'; *s += strspn(*s + 1, WHITESPACE) + 1; return (old); } } /* Allow only one '=' to be skipped */ if (*s[0] == '=') wspace = 1; *s[0] = '\0'; /* Skip any extra whitespace after first token */ *s += strspn(*s + 1, WHITESPACE) + 1; if (*s[0] == '=' && !wspace) *s += strspn(*s + 1, WHITESPACE) + 1; return (old); } struct passwd * pwcopy(struct passwd *pw) { struct passwd *copy = xcalloc(1, sizeof(*copy)); copy->pw_name = xstrdup(pw->pw_name); copy->pw_passwd = xstrdup(pw->pw_passwd); copy->pw_gecos = xstrdup(pw->pw_gecos); copy->pw_uid = pw->pw_uid; copy->pw_gid = pw->pw_gid; #ifdef HAVE_PW_EXPIRE_IN_PASSWD copy->pw_expire = pw->pw_expire; #endif #ifdef HAVE_PW_CHANGE_IN_PASSWD copy->pw_change = pw->pw_change; #endif #ifdef HAVE_PW_CLASS_IN_PASSWD copy->pw_class = xstrdup(pw->pw_class); #endif copy->pw_dir = xstrdup(pw->pw_dir); copy->pw_shell = xstrdup(pw->pw_shell); return copy; } /* * Convert ASCII string to TCP/IP port number. * Port must be >=0 and <=65535. * Return -1 if invalid. */ int a2port(const char *s) { long long port; const char *errstr; port = strtonum(s, 0, 65535, &errstr); if (errstr != NULL) return -1; return (int)port; } int a2tun(const char *s, int *remote) { const char *errstr = NULL; char *sp, *ep; int tun; if (remote != NULL) { *remote = SSH_TUNID_ANY; sp = xstrdup(s); if ((ep = strchr(sp, ':')) == NULL) { xfree(sp); return (a2tun(s, NULL)); } ep[0] = '\0'; ep++; *remote = a2tun(ep, NULL); tun = a2tun(sp, NULL); xfree(sp); return (*remote == SSH_TUNID_ERR ? *remote : tun); } if (strcasecmp(s, "any") == 0) return (SSH_TUNID_ANY); tun = strtonum(s, 0, SSH_TUNID_MAX, &errstr); if (errstr != NULL) return (SSH_TUNID_ERR); return (tun); } #define SECONDS 1 #define MINUTES (SECONDS * 60) #define HOURS (MINUTES * 60) #define DAYS (HOURS * 24) #define WEEKS (DAYS * 7) /* * Convert a time string into seconds; format is * a sequence of: * time[qualifier] * * Valid time qualifiers are: * seconds * s|S seconds * m|M minutes * h|H hours * d|D days * w|W weeks * * Examples: * 90m 90 minutes * 1h30m 90 minutes * 2d 2 days * 1w 1 week * * Return -1 if time string is invalid. */ long convtime(const char *s) { long total, secs; const char *p; char *endp; errno = 0; total = 0; p = s; if (p == NULL || *p == '\0') return -1; while (*p) { secs = strtol(p, &endp, 10); if (p == endp || (errno == ERANGE && (secs == LONG_MIN || secs == LONG_MAX)) || secs < 0) return -1; switch (*endp++) { case '\0': endp--; break; case 's': case 'S': break; case 'm': case 'M': secs *= MINUTES; break; case 'h': case 'H': secs *= HOURS; break; case 'd': case 'D': secs *= DAYS; break; case 'w': case 'W': secs *= WEEKS; break; default: return -1; } total += secs; if (total < 0) return -1; p = endp; } return total; } /* * Returns a standardized host+port identifier string. * Caller must free returned string. */ char * put_host_port(const char *host, u_short port) { char *hoststr; if (port == 0 || port == SSH_DEFAULT_PORT) return(xstrdup(host)); if (asprintf(&hoststr, "[%s]:%d", host, (int)port) < 0) fatal("put_host_port: asprintf: %s", strerror(errno)); debug3("put_host_port: %s", hoststr); return hoststr; } /* * Search for next delimiter between hostnames/addresses and ports. * Argument may be modified (for termination). * Returns *cp if parsing succeeds. * *cp is set to the start of the next delimiter, if one was found. * If this is the last field, *cp is set to NULL. */ char * hpdelim(char **cp) { char *s, *old; if (cp == NULL || *cp == NULL) return NULL; old = s = *cp; if (*s == '[') { if ((s = strchr(s, ']')) == NULL) return NULL; else s++; } else if ((s = strpbrk(s, ":/")) == NULL) s = *cp + strlen(*cp); /* skip to end (see first case below) */ switch (*s) { case '\0': *cp = NULL; /* no more fields*/ break; case ':': case '/': *s = '\0'; /* terminate */ *cp = s + 1; break; default: return NULL; } return old; } char * cleanhostname(char *host) { if (*host == '[' && host[strlen(host) - 1] == ']') { host[strlen(host) - 1] = '\0'; return (host + 1); } else return host; } char * colon(char *cp) { int flag = 0; if (*cp == ':') /* Leading colon is part of file name. */ return NULL; if (*cp == '[') flag = 1; for (; *cp; ++cp) { if (*cp == '@' && *(cp+1) == '[') flag = 1; if (*cp == ']' && *(cp+1) == ':' && flag) return (cp+1); if (*cp == ':' && !flag) return (cp); if (*cp == '/') return NULL; } return NULL; } /* function to assist building execv() arguments */ void addargs(arglist *args, char *fmt, ...) { va_list ap; char *cp; u_int nalloc; int r; va_start(ap, fmt); r = vasprintf(&cp, fmt, ap); va_end(ap); if (r == -1) fatal("addargs: argument too long"); nalloc = args->nalloc; if (args->list == NULL) { nalloc = 32; args->num = 0; } else if (args->num+2 >= nalloc) nalloc *= 2; args->list = xrealloc(args->list, nalloc, sizeof(char *)); args->nalloc = nalloc; args->list[args->num++] = cp; args->list[args->num] = NULL; } void replacearg(arglist *args, u_int which, char *fmt, ...) { va_list ap; char *cp; int r; va_start(ap, fmt); r = vasprintf(&cp, fmt, ap); va_end(ap); if (r == -1) fatal("replacearg: argument too long"); if (which >= args->num) fatal("replacearg: tried to replace invalid arg %d >= %d", which, args->num); xfree(args->list[which]); args->list[which] = cp; } void freeargs(arglist *args) { u_int i; if (args->list != NULL) { for (i = 0; i < args->num; i++) xfree(args->list[i]); xfree(args->list); args->nalloc = args->num = 0; args->list = NULL; } } /* * Expands tildes in the file name. Returns data allocated by xmalloc. * Warning: this calls getpw*. */ char * tilde_expand_filename(const char *filename, uid_t uid) { const char *path; char user[128], ret[MAXPATHLEN]; struct passwd *pw; u_int len, slash; if (*filename != '~') return (xstrdup(filename)); filename++; path = strchr(filename, '/'); if (path != NULL && path > filename) { /* ~user/path */ slash = path - filename; if (slash > sizeof(user) - 1) fatal("tilde_expand_filename: ~username too long"); memcpy(user, filename, slash); user[slash] = '\0'; if ((pw = getpwnam(user)) == NULL) fatal("tilde_expand_filename: No such user %s", user); } else if ((pw = getpwuid(uid)) == NULL) /* ~/path */ fatal("tilde_expand_filename: No such uid %ld", (long)uid); if (strlcpy(ret, pw->pw_dir, sizeof(ret)) >= sizeof(ret)) fatal("tilde_expand_filename: Path too long"); /* Make sure directory has a trailing '/' */ len = strlen(pw->pw_dir); if ((len == 0 || pw->pw_dir[len - 1] != '/') && strlcat(ret, "/", sizeof(ret)) >= sizeof(ret)) fatal("tilde_expand_filename: Path too long"); /* Skip leading '/' from specified path */ if (path != NULL) filename = path + 1; if (strlcat(ret, filename, sizeof(ret)) >= sizeof(ret)) fatal("tilde_expand_filename: Path too long"); return (xstrdup(ret)); } /* * Expand a string with a set of %[char] escapes. A number of escapes may be * specified as (char *escape_chars, char *replacement) pairs. The list must * be terminated by a NULL escape_char. Returns replaced string in memory * allocated by xmalloc. */ char * percent_expand(const char *string, ...) { #define EXPAND_MAX_KEYS 16 u_int num_keys, i, j; struct { const char *key; const char *repl; } keys[EXPAND_MAX_KEYS]; char buf[4096]; va_list ap; /* Gather keys */ va_start(ap, string); for (num_keys = 0; num_keys < EXPAND_MAX_KEYS; num_keys++) { keys[num_keys].key = va_arg(ap, char *); if (keys[num_keys].key == NULL) break; keys[num_keys].repl = va_arg(ap, char *); if (keys[num_keys].repl == NULL) fatal("%s: NULL replacement", __func__); } if (num_keys == EXPAND_MAX_KEYS && va_arg(ap, char *) != NULL) fatal("%s: too many keys", __func__); va_end(ap); /* Expand string */ *buf = '\0'; for (i = 0; *string != '\0'; string++) { if (*string != '%') { append: buf[i++] = *string; if (i >= sizeof(buf)) fatal("%s: string too long", __func__); buf[i] = '\0'; continue; } string++; /* %% case */ if (*string == '%') goto append; for (j = 0; j < num_keys; j++) { if (strchr(keys[j].key, *string) != NULL) { i = strlcat(buf, keys[j].repl, sizeof(buf)); if (i >= sizeof(buf)) fatal("%s: string too long", __func__); break; } } if (j >= num_keys) fatal("%s: unknown key %%%c", __func__, *string); } return (xstrdup(buf)); #undef EXPAND_MAX_KEYS } /* * Read an entire line from a public key file into a static buffer, discarding * lines that exceed the buffer size. Returns 0 on success, -1 on failure. */ int read_keyfile_line(FILE *f, const char *filename, char *buf, size_t bufsz, u_long *lineno) { while (fgets(buf, bufsz, f) != NULL) { if (buf[0] == '\0') continue; (*lineno)++; if (buf[strlen(buf) - 1] == '\n' || feof(f)) { return 0; } else { debug("%s: %s line %lu exceeds size limit", __func__, filename, *lineno); /* discard remainder of line */ while (fgetc(f) != '\n' && !feof(f)) ; /* nothing */ } } return -1; } int tun_open(int tun, int mode) { #if defined(CUSTOM_SYS_TUN_OPEN) return (sys_tun_open(tun, mode)); #elif defined(SSH_TUN_OPENBSD) struct ifreq ifr; char name[100]; int fd = -1, sock; /* Open the tunnel device */ if (tun <= SSH_TUNID_MAX) { snprintf(name, sizeof(name), "/dev/tun%d", tun); fd = open(name, O_RDWR); } else if (tun == SSH_TUNID_ANY) { for (tun = 100; tun >= 0; tun--) { snprintf(name, sizeof(name), "/dev/tun%d", tun); if ((fd = open(name, O_RDWR)) >= 0) break; } } else { debug("%s: invalid tunnel %u", __func__, tun); return (-1); } if (fd < 0) { debug("%s: %s open failed: %s", __func__, name, strerror(errno)); return (-1); } debug("%s: %s mode %d fd %d", __func__, name, mode, fd); /* Set the tunnel device operation mode */ snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "tun%d", tun); if ((sock = socket(PF_UNIX, SOCK_STREAM, 0)) == -1) goto failed; if (ioctl(sock, SIOCGIFFLAGS, &ifr) == -1) goto failed; /* Set interface mode */ ifr.ifr_flags &= ~IFF_UP; if (mode == SSH_TUNMODE_ETHERNET) ifr.ifr_flags |= IFF_LINK0; else ifr.ifr_flags &= ~IFF_LINK0; if (ioctl(sock, SIOCSIFFLAGS, &ifr) == -1) goto failed; /* Bring interface up */ ifr.ifr_flags |= IFF_UP; if (ioctl(sock, SIOCSIFFLAGS, &ifr) == -1) goto failed; close(sock); return (fd); failed: if (fd >= 0) close(fd); if (sock >= 0) close(sock); debug("%s: failed to set %s mode %d: %s", __func__, name, mode, strerror(errno)); return (-1); #else error("Tunnel interfaces are not supported on this platform"); return (-1); #endif } void sanitise_stdfd(void) { int nullfd, dupfd; if ((nullfd = dupfd = open(_PATH_DEVNULL, O_RDWR)) == -1) { fprintf(stderr, "Couldn't open /dev/null: %s\n", strerror(errno)); exit(1); } while (++dupfd <= 2) { /* Only clobber closed fds */ if (fcntl(dupfd, F_GETFL, 0) >= 0) continue; if (dup2(nullfd, dupfd) == -1) { fprintf(stderr, "dup2: %s\n", strerror(errno)); exit(1); } } if (nullfd > 2) close(nullfd); } char * tohex(const void *vp, size_t l) { const u_char *p = (const u_char *)vp; char b[3], *r; size_t i, hl; if (l > 65536) return xstrdup("tohex: length > 65536"); hl = l * 2 + 1; r = xcalloc(1, hl); for (i = 0; i < l; i++) { snprintf(b, sizeof(b), "%02x", p[i]); strlcat(r, b, hl); } return (r); } u_int64_t get_u64(const void *vp) { const u_char *p = (const u_char *)vp; u_int64_t v; v = (u_int64_t)p[0] << 56; v |= (u_int64_t)p[1] << 48; v |= (u_int64_t)p[2] << 40; v |= (u_int64_t)p[3] << 32; v |= (u_int64_t)p[4] << 24; v |= (u_int64_t)p[5] << 16; v |= (u_int64_t)p[6] << 8; v |= (u_int64_t)p[7]; return (v); } u_int32_t get_u32(const void *vp) { const u_char *p = (const u_char *)vp; u_int32_t v; v = (u_int32_t)p[0] << 24; v |= (u_int32_t)p[1] << 16; v |= (u_int32_t)p[2] << 8; v |= (u_int32_t)p[3]; return (v); } u_int16_t get_u16(const void *vp) { const u_char *p = (const u_char *)vp; u_int16_t v; v = (u_int16_t)p[0] << 8; v |= (u_int16_t)p[1]; return (v); } void put_u64(void *vp, u_int64_t v) { u_char *p = (u_char *)vp; p[0] = (u_char)(v >> 56) & 0xff; p[1] = (u_char)(v >> 48) & 0xff; p[2] = (u_char)(v >> 40) & 0xff; p[3] = (u_char)(v >> 32) & 0xff; p[4] = (u_char)(v >> 24) & 0xff; p[5] = (u_char)(v >> 16) & 0xff; p[6] = (u_char)(v >> 8) & 0xff; p[7] = (u_char)v & 0xff; } void put_u32(void *vp, u_int32_t v) { u_char *p = (u_char *)vp; p[0] = (u_char)(v >> 24) & 0xff; p[1] = (u_char)(v >> 16) & 0xff; p[2] = (u_char)(v >> 8) & 0xff; p[3] = (u_char)v & 0xff; } void put_u16(void *vp, u_int16_t v) { u_char *p = (u_char *)vp; p[0] = (u_char)(v >> 8) & 0xff; p[1] = (u_char)v & 0xff; } void ms_subtract_diff(struct timeval *start, int *ms) { struct timeval diff, finish; gettimeofday(&finish, NULL); timersub(&finish, start, &diff); *ms -= (diff.tv_sec * 1000) + (diff.tv_usec / 1000); } void ms_to_timeval(struct timeval *tv, int ms) { if (ms < 0) ms = 0; tv->tv_sec = ms / 1000; tv->tv_usec = (ms % 1000) * 1000; } void bandwidth_limit_init(struct bwlimit *bw, u_int64_t kbps, size_t buflen) { bw->buflen = buflen; bw->rate = kbps; bw->thresh = bw->rate; bw->lamt = 0; timerclear(&bw->bwstart); timerclear(&bw->bwend); } /* Callback from read/write loop to insert bandwidth-limiting delays */ void bandwidth_limit(struct bwlimit *bw, size_t read_len) { u_int64_t waitlen; struct timespec ts, rm; if (!timerisset(&bw->bwstart)) { gettimeofday(&bw->bwstart, NULL); return; } bw->lamt += read_len; if (bw->lamt < bw->thresh) return; gettimeofday(&bw->bwend, NULL); timersub(&bw->bwend, &bw->bwstart, &bw->bwend); if (!timerisset(&bw->bwend)) return; bw->lamt *= 8; waitlen = (double)1000000L * bw->lamt / bw->rate; bw->bwstart.tv_sec = waitlen / 1000000L; bw->bwstart.tv_usec = waitlen % 1000000L; if (timercmp(&bw->bwstart, &bw->bwend, >)) { timersub(&bw->bwstart, &bw->bwend, &bw->bwend); /* Adjust the wait time */ if (bw->bwend.tv_sec) { bw->thresh /= 2; if (bw->thresh < bw->buflen / 4) bw->thresh = bw->buflen / 4; } else if (bw->bwend.tv_usec < 10000) { bw->thresh *= 2; if (bw->thresh > bw->buflen * 8) bw->thresh = bw->buflen * 8; } TIMEVAL_TO_TIMESPEC(&bw->bwend, &ts); while (nanosleep(&ts, &rm) == -1) { if (errno != EINTR) break; ts = rm; } } bw->lamt = 0; gettimeofday(&bw->bwstart, NULL); } /* Make a template filename for mk[sd]temp() */ void mktemp_proto(char *s, size_t len) { const char *tmpdir; int r; if ((tmpdir = getenv("TMPDIR")) != NULL) { r = snprintf(s, len, "%s/ssh-XXXXXXXXXXXX", tmpdir); if (r > 0 && (size_t)r < len) return; } r = snprintf(s, len, "/tmp/ssh-XXXXXXXXXXXX"); if (r < 0 || (size_t)r >= len) fatal("%s: template string too short", __func__); } static const struct { const char *name; int value; } ipqos[] = { { "af11", IPTOS_DSCP_AF11 }, { "af12", IPTOS_DSCP_AF12 }, { "af13", IPTOS_DSCP_AF13 }, { "af14", IPTOS_DSCP_AF21 }, { "af22", IPTOS_DSCP_AF22 }, { "af23", IPTOS_DSCP_AF23 }, { "af31", IPTOS_DSCP_AF31 }, { "af32", IPTOS_DSCP_AF32 }, { "af33", IPTOS_DSCP_AF33 }, { "af41", IPTOS_DSCP_AF41 }, { "af42", IPTOS_DSCP_AF42 }, { "af43", IPTOS_DSCP_AF43 }, { "cs0", IPTOS_DSCP_CS0 }, { "cs1", IPTOS_DSCP_CS1 }, { "cs2", IPTOS_DSCP_CS2 }, { "cs3", IPTOS_DSCP_CS3 }, { "cs4", IPTOS_DSCP_CS4 }, { "cs5", IPTOS_DSCP_CS5 }, { "cs6", IPTOS_DSCP_CS6 }, { "cs7", IPTOS_DSCP_CS7 }, { "ef", IPTOS_DSCP_EF }, { "lowdelay", IPTOS_LOWDELAY }, { "throughput", IPTOS_THROUGHPUT }, { "reliability", IPTOS_RELIABILITY }, { NULL, -1 } }; int parse_ipqos(const char *cp) { u_int i; char *ep; long val; if (cp == NULL) return -1; for (i = 0; ipqos[i].name != NULL; i++) { if (strcasecmp(cp, ipqos[i].name) == 0) return ipqos[i].value; } /* Try parsing as an integer */ val = strtol(cp, &ep, 0); if (*cp == '\0' || *ep != '\0' || val < 0 || val > 255) return -1; return val; } void sock_set_v6only(int s) { #ifdef IPV6_V6ONLY int on = 1; debug3("%s: set socket %d IPV6_V6ONLY", __func__, s); if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)) == -1) error("setsockopt IPV6_V6ONLY: %s", strerror(errno)); #endif } + +void +sock_get_rcvbuf(int *size, int rcvbuf) +{ + int sock, socksize; + socklen_t socksizelen = sizeof(socksize); + + /* + * Create a socket but do not connect it. We use it + * only to get the rcv socket size. + */ + sock = socket(AF_INET6, SOCK_STREAM, 0); + if (sock < 0) + sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock < 0) + return; + + /* + * If the tcp_rcv_buf option is set and passed in, attempt to set the + * buffer size to its value. + */ + if (rcvbuf) + setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (void *)&rcvbuf, + sizeof(rcvbuf)); + + if (getsockopt(sock, SOL_SOCKET, SO_RCVBUF, + &socksize, &socksizelen) == 0) + if (size != NULL) + *size = socksize; + close(sock); +} diff --git a/crypto/openssh/misc.h b/crypto/openssh/misc.h index 65cf4a6163f3..19149ed3fc20 100644 --- a/crypto/openssh/misc.h +++ b/crypto/openssh/misc.h @@ -1,105 +1,107 @@ /* $OpenBSD: misc.h,v 1.47 2010/11/21 01:01:13 djm Exp $ */ +/* $FreeBSD$ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved * * As far as I am concerned, the code I have written for this software * can be used freely for any purpose. Any derived versions of this * software must be clearly marked as such, and if the derived work is * incompatible with the protocol description in the RFC file, it must be * called by a name other than "ssh" or "Secure Shell". */ #ifndef _MISC_H #define _MISC_H /* misc.c */ char *chop(char *); char *strdelim(char **); int set_nonblock(int); int unset_nonblock(int); void set_nodelay(int); int a2port(const char *); int a2tun(const char *, int *); char *put_host_port(const char *, u_short); char *hpdelim(char **); char *cleanhostname(char *); char *colon(char *); long convtime(const char *); char *tilde_expand_filename(const char *, uid_t); char *percent_expand(const char *, ...) __attribute__((__sentinel__)); char *tohex(const void *, size_t); void sanitise_stdfd(void); void ms_subtract_diff(struct timeval *, int *); void ms_to_timeval(struct timeval *, int); void sock_set_v6only(int); +void sock_get_rcvbuf(int *, int); struct passwd *pwcopy(struct passwd *); const char *ssh_gai_strerror(int); typedef struct arglist arglist; struct arglist { char **list; u_int num; u_int nalloc; }; void addargs(arglist *, char *, ...) __attribute__((format(printf, 2, 3))); void replacearg(arglist *, u_int, char *, ...) __attribute__((format(printf, 3, 4))); void freeargs(arglist *); int tun_open(int, int); /* Common definitions for ssh tunnel device forwarding */ #define SSH_TUNMODE_NO 0x00 #define SSH_TUNMODE_POINTOPOINT 0x01 #define SSH_TUNMODE_ETHERNET 0x02 #define SSH_TUNMODE_DEFAULT SSH_TUNMODE_POINTOPOINT #define SSH_TUNMODE_YES (SSH_TUNMODE_POINTOPOINT|SSH_TUNMODE_ETHERNET) #define SSH_TUNID_ANY 0x7fffffff #define SSH_TUNID_ERR (SSH_TUNID_ANY - 1) #define SSH_TUNID_MAX (SSH_TUNID_ANY - 2) /* Functions to extract or store big-endian words of various sizes */ u_int64_t get_u64(const void *) __attribute__((__bounded__( __minbytes__, 1, 8))); u_int32_t get_u32(const void *) __attribute__((__bounded__( __minbytes__, 1, 4))); u_int16_t get_u16(const void *) __attribute__((__bounded__( __minbytes__, 1, 2))); void put_u64(void *, u_int64_t) __attribute__((__bounded__( __minbytes__, 1, 8))); void put_u32(void *, u_int32_t) __attribute__((__bounded__( __minbytes__, 1, 4))); void put_u16(void *, u_int16_t) __attribute__((__bounded__( __minbytes__, 1, 2))); struct bwlimit { size_t buflen; u_int64_t rate, thresh, lamt; struct timeval bwstart, bwend; }; void bandwidth_limit_init(struct bwlimit *, u_int64_t, size_t); void bandwidth_limit(struct bwlimit *, size_t); int parse_ipqos(const char *); void mktemp_proto(char *, size_t); /* readpass.c */ #define RP_ECHO 0x0001 #define RP_ALLOW_STDIN 0x0002 #define RP_ALLOW_EOF 0x0004 #define RP_USE_ASKPASS 0x0008 char *read_passphrase(const char *, int); int ask_permission(const char *, ...) __attribute__((format(printf, 1, 2))); int read_keyfile_line(FILE *, const char *, char *, size_t, u_long *); #endif /* _MISC_H */ diff --git a/crypto/openssh/myproposal.h b/crypto/openssh/myproposal.h index 2c43607a7bd6..30948c2f9e24 100644 --- a/crypto/openssh/myproposal.h +++ b/crypto/openssh/myproposal.h @@ -1,97 +1,102 @@ /* $OpenBSD: myproposal.h,v 1.27 2010/09/01 22:42:13 djm Exp $ */ +/* $FreeBSD$ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #ifdef OPENSSL_HAS_ECC # define KEX_ECDH_METHODS \ "ecdh-sha2-nistp256," \ "ecdh-sha2-nistp384," \ "ecdh-sha2-nistp521," # define HOSTKEY_ECDSA_CERT_METHODS \ "ecdsa-sha2-nistp256-cert-v01@openssh.com," \ "ecdsa-sha2-nistp384-cert-v01@openssh.com," \ "ecdsa-sha2-nistp521-cert-v01@openssh.com," # define HOSTKEY_ECDSA_METHODS \ "ecdsa-sha2-nistp256," \ "ecdsa-sha2-nistp384," \ "ecdsa-sha2-nistp521," #else # define KEX_ECDH_METHODS # define HOSTKEY_ECDSA_CERT_METHODS # define HOSTKEY_ECDSA_METHODS #endif /* Old OpenSSL doesn't support what we need for DHGEX-sha256 */ #if OPENSSL_VERSION_NUMBER >= 0x00907000L # define KEX_SHA256_METHODS \ "diffie-hellman-group-exchange-sha256," #else # define KEX_SHA256_METHODS #endif # define KEX_DEFAULT_KEX \ KEX_ECDH_METHODS \ KEX_SHA256_METHODS \ "diffie-hellman-group-exchange-sha1," \ "diffie-hellman-group14-sha1," \ "diffie-hellman-group1-sha1" #define KEX_DEFAULT_PK_ALG \ HOSTKEY_ECDSA_CERT_METHODS \ "ssh-rsa-cert-v01@openssh.com," \ "ssh-dss-cert-v01@openssh.com," \ "ssh-rsa-cert-v00@openssh.com," \ "ssh-dss-cert-v00@openssh.com," \ HOSTKEY_ECDSA_METHODS \ "ssh-rsa," \ "ssh-dss" #define KEX_DEFAULT_ENCRYPT \ "aes128-ctr,aes192-ctr,aes256-ctr," \ "arcfour256,arcfour128," \ "aes128-cbc,3des-cbc,blowfish-cbc,cast128-cbc," \ "aes192-cbc,aes256-cbc,arcfour,rijndael-cbc@lysator.liu.se" +#ifdef NONE_CIPHER_ENABLED +#define KEX_ENCRYPT_INCLUDE_NONE KEX_DEFAULT_ENCRYPT \ + ",none" +#endif #define KEX_DEFAULT_MAC \ "hmac-md5,hmac-sha1,umac-64@openssh.com,hmac-ripemd160," \ "hmac-ripemd160@openssh.com," \ "hmac-sha1-96,hmac-md5-96" #define KEX_DEFAULT_COMP "none,zlib@openssh.com,zlib" #define KEX_DEFAULT_LANG "" static char *myproposal[PROPOSAL_MAX] = { KEX_DEFAULT_KEX, KEX_DEFAULT_PK_ALG, KEX_DEFAULT_ENCRYPT, KEX_DEFAULT_ENCRYPT, KEX_DEFAULT_MAC, KEX_DEFAULT_MAC, KEX_DEFAULT_COMP, KEX_DEFAULT_COMP, KEX_DEFAULT_LANG, KEX_DEFAULT_LANG }; diff --git a/crypto/openssh/packet.c b/crypto/openssh/packet.c index b4e01f716b47..6f2bdc390af9 100644 --- a/crypto/openssh/packet.c +++ b/crypto/openssh/packet.c @@ -1,1960 +1,1986 @@ /* $OpenBSD: packet.c,v 1.172 2010/11/13 23:27:50 djm Exp $ */ +/* $FreeBSD$ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved * This file contains code implementing the packet protocol and communication * with the other side. This same code is used both on client and server side. * * As far as I am concerned, the code I have written for this software * can be used freely for any purpose. Any derived versions of this * software must be clearly marked as such, and if the derived work is * incompatible with the protocol description in the RFC file, it must be * called by a name other than "ssh" or "Secure Shell". * * * SSH2 packet format added by Markus Friedl. * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "includes.h" #include #include "openbsd-compat/sys-queue.h" #include #include #ifdef HAVE_SYS_TIME_H # include #endif #include #include #include #include #include #include #include #include #include #include #include "xmalloc.h" #include "buffer.h" #include "packet.h" #include "crc32.h" #include "compress.h" #include "deattack.h" #include "channels.h" #include "compat.h" #include "ssh1.h" #include "ssh2.h" #include "cipher.h" #include "key.h" #include "kex.h" #include "mac.h" #include "log.h" #include "canohost.h" #include "misc.h" #include "ssh.h" #include "roaming.h" #ifdef PACKET_DEBUG #define DBG(x) x #else #define DBG(x) #endif #define PACKET_MAX_SIZE (256 * 1024) struct packet_state { u_int32_t seqnr; u_int32_t packets; u_int64_t blocks; u_int64_t bytes; }; struct packet { TAILQ_ENTRY(packet) next; u_char type; Buffer payload; }; struct session_state { /* * This variable contains the file descriptors used for * communicating with the other side. connection_in is used for * reading; connection_out for writing. These can be the same * descriptor, in which case it is assumed to be a socket. */ int connection_in; int connection_out; /* Protocol flags for the remote side. */ u_int remote_protocol_flags; /* Encryption context for receiving data. Only used for decryption. */ CipherContext receive_context; /* Encryption context for sending data. Only used for encryption. */ CipherContext send_context; /* Buffer for raw input data from the socket. */ Buffer input; /* Buffer for raw output data going to the socket. */ Buffer output; /* Buffer for the partial outgoing packet being constructed. */ Buffer outgoing_packet; /* Buffer for the incoming packet currently being processed. */ Buffer incoming_packet; /* Scratch buffer for packet compression/decompression. */ Buffer compression_buffer; int compression_buffer_ready; /* * Flag indicating whether packet compression/decompression is * enabled. */ int packet_compression; /* default maximum packet size */ u_int max_packet_size; /* Flag indicating whether this module has been initialized. */ int initialized; /* Set to true if the connection is interactive. */ int interactive_mode; /* Set to true if we are the server side. */ int server_side; /* Set to true if we are authenticated. */ int after_authentication; int keep_alive_timeouts; /* The maximum time that we will wait to send or receive a packet */ int packet_timeout_ms; /* Session key information for Encryption and MAC */ Newkeys *newkeys[MODE_MAX]; struct packet_state p_read, p_send; u_int64_t max_blocks_in, max_blocks_out; u_int32_t rekey_limit; /* Session key for protocol v1 */ u_char ssh1_key[SSH_SESSION_KEY_LENGTH]; u_int ssh1_keylen; /* roundup current message to extra_pad bytes */ u_char extra_pad; /* XXX discard incoming data after MAC error */ u_int packet_discard; Mac *packet_discard_mac; /* Used in packet_read_poll2() */ u_int packlen; /* Used in packet_send2 */ int rekeying; /* Used in packet_set_interactive */ int set_interactive_called; /* Used in packet_set_maxsize */ int set_maxsize_called; TAILQ_HEAD(, packet) outgoing; }; static struct session_state *active_state, *backup_state; +#ifdef NONE_CIPHER_ENABLED +static int rekey_requested = 0; +#endif static struct session_state * alloc_session_state(void) { struct session_state *s = xcalloc(1, sizeof(*s)); s->connection_in = -1; s->connection_out = -1; s->max_packet_size = 32768; s->packet_timeout_ms = -1; return s; } /* * Sets the descriptors used for communication. Disables encryption until * packet_set_encryption_key is called. */ void packet_set_connection(int fd_in, int fd_out) { Cipher *none = cipher_by_name("none"); if (none == NULL) fatal("packet_set_connection: cannot load cipher 'none'"); if (active_state == NULL) active_state = alloc_session_state(); active_state->connection_in = fd_in; active_state->connection_out = fd_out; cipher_init(&active_state->send_context, none, (const u_char *)"", 0, NULL, 0, CIPHER_ENCRYPT); cipher_init(&active_state->receive_context, none, (const u_char *)"", 0, NULL, 0, CIPHER_DECRYPT); active_state->newkeys[MODE_IN] = active_state->newkeys[MODE_OUT] = NULL; if (!active_state->initialized) { active_state->initialized = 1; buffer_init(&active_state->input); buffer_init(&active_state->output); buffer_init(&active_state->outgoing_packet); buffer_init(&active_state->incoming_packet); TAILQ_INIT(&active_state->outgoing); active_state->p_send.packets = active_state->p_read.packets = 0; } } void packet_set_timeout(int timeout, int count) { if (timeout == 0 || count == 0) { active_state->packet_timeout_ms = -1; return; } if ((INT_MAX / 1000) / count < timeout) active_state->packet_timeout_ms = INT_MAX; else active_state->packet_timeout_ms = timeout * count * 1000; } static void packet_stop_discard(void) { if (active_state->packet_discard_mac) { char buf[1024]; memset(buf, 'a', sizeof(buf)); while (buffer_len(&active_state->incoming_packet) < PACKET_MAX_SIZE) buffer_append(&active_state->incoming_packet, buf, sizeof(buf)); (void) mac_compute(active_state->packet_discard_mac, active_state->p_read.seqnr, buffer_ptr(&active_state->incoming_packet), PACKET_MAX_SIZE); } logit("Finished discarding for %.200s", get_remote_ipaddr()); cleanup_exit(255); } static void packet_start_discard(Enc *enc, Mac *mac, u_int packet_length, u_int discard) { if (enc == NULL || !cipher_is_cbc(enc->cipher)) packet_disconnect("Packet corrupt"); if (packet_length != PACKET_MAX_SIZE && mac && mac->enabled) active_state->packet_discard_mac = mac; if (buffer_len(&active_state->input) >= discard) packet_stop_discard(); active_state->packet_discard = discard - buffer_len(&active_state->input); } /* Returns 1 if remote host is connected via socket, 0 if not. */ int packet_connection_is_on_socket(void) { struct sockaddr_storage from, to; socklen_t fromlen, tolen; /* filedescriptors in and out are the same, so it's a socket */ if (active_state->connection_in == active_state->connection_out) return 1; fromlen = sizeof(from); memset(&from, 0, sizeof(from)); if (getpeername(active_state->connection_in, (struct sockaddr *)&from, &fromlen) < 0) return 0; tolen = sizeof(to); memset(&to, 0, sizeof(to)); if (getpeername(active_state->connection_out, (struct sockaddr *)&to, &tolen) < 0) return 0; if (fromlen != tolen || memcmp(&from, &to, fromlen) != 0) return 0; if (from.ss_family != AF_INET && from.ss_family != AF_INET6) return 0; return 1; } /* * Exports an IV from the CipherContext required to export the key * state back from the unprivileged child to the privileged parent * process. */ void packet_get_keyiv(int mode, u_char *iv, u_int len) { CipherContext *cc; if (mode == MODE_OUT) cc = &active_state->send_context; else cc = &active_state->receive_context; cipher_get_keyiv(cc, iv, len); } int packet_get_keycontext(int mode, u_char *dat) { CipherContext *cc; if (mode == MODE_OUT) cc = &active_state->send_context; else cc = &active_state->receive_context; return (cipher_get_keycontext(cc, dat)); } void packet_set_keycontext(int mode, u_char *dat) { CipherContext *cc; if (mode == MODE_OUT) cc = &active_state->send_context; else cc = &active_state->receive_context; cipher_set_keycontext(cc, dat); } int packet_get_keyiv_len(int mode) { CipherContext *cc; if (mode == MODE_OUT) cc = &active_state->send_context; else cc = &active_state->receive_context; return (cipher_get_keyiv_len(cc)); } void packet_set_iv(int mode, u_char *dat) { CipherContext *cc; if (mode == MODE_OUT) cc = &active_state->send_context; else cc = &active_state->receive_context; cipher_set_keyiv(cc, dat); } int packet_get_ssh1_cipher(void) { return (cipher_get_number(active_state->receive_context.cipher)); } void packet_get_state(int mode, u_int32_t *seqnr, u_int64_t *blocks, u_int32_t *packets, u_int64_t *bytes) { struct packet_state *state; state = (mode == MODE_IN) ? &active_state->p_read : &active_state->p_send; if (seqnr) *seqnr = state->seqnr; if (blocks) *blocks = state->blocks; if (packets) *packets = state->packets; if (bytes) *bytes = state->bytes; } void packet_set_state(int mode, u_int32_t seqnr, u_int64_t blocks, u_int32_t packets, u_int64_t bytes) { struct packet_state *state; state = (mode == MODE_IN) ? &active_state->p_read : &active_state->p_send; state->seqnr = seqnr; state->blocks = blocks; state->packets = packets; state->bytes = bytes; } /* returns 1 if connection is via ipv4 */ int packet_connection_is_ipv4(void) { struct sockaddr_storage to; socklen_t tolen = sizeof(to); memset(&to, 0, sizeof(to)); if (getsockname(active_state->connection_out, (struct sockaddr *)&to, &tolen) < 0) return 0; if (to.ss_family == AF_INET) return 1; #ifdef IPV4_IN_IPV6 if (to.ss_family == AF_INET6 && IN6_IS_ADDR_V4MAPPED(&((struct sockaddr_in6 *)&to)->sin6_addr)) return 1; #endif return 0; } /* Sets the connection into non-blocking mode. */ void packet_set_nonblocking(void) { /* Set the socket into non-blocking mode. */ set_nonblock(active_state->connection_in); if (active_state->connection_out != active_state->connection_in) set_nonblock(active_state->connection_out); } /* Returns the socket used for reading. */ int packet_get_connection_in(void) { return active_state->connection_in; } /* Returns the descriptor used for writing. */ int packet_get_connection_out(void) { return active_state->connection_out; } /* Closes the connection and clears and frees internal data structures. */ void packet_close(void) { if (!active_state->initialized) return; active_state->initialized = 0; if (active_state->connection_in == active_state->connection_out) { shutdown(active_state->connection_out, SHUT_RDWR); close(active_state->connection_out); } else { close(active_state->connection_in); close(active_state->connection_out); } buffer_free(&active_state->input); buffer_free(&active_state->output); buffer_free(&active_state->outgoing_packet); buffer_free(&active_state->incoming_packet); if (active_state->compression_buffer_ready) { buffer_free(&active_state->compression_buffer); buffer_compress_uninit(); } cipher_cleanup(&active_state->send_context); cipher_cleanup(&active_state->receive_context); } /* Sets remote side protocol flags. */ void packet_set_protocol_flags(u_int protocol_flags) { active_state->remote_protocol_flags = protocol_flags; } /* Returns the remote protocol flags set earlier by the above function. */ u_int packet_get_protocol_flags(void) { return active_state->remote_protocol_flags; } /* * Starts packet compression from the next packet on in both directions. * Level is compression level 1 (fastest) - 9 (slow, best) as in gzip. */ static void packet_init_compression(void) { if (active_state->compression_buffer_ready == 1) return; active_state->compression_buffer_ready = 1; buffer_init(&active_state->compression_buffer); } void packet_start_compression(int level) { if (active_state->packet_compression && !compat20) fatal("Compression already enabled."); active_state->packet_compression = 1; packet_init_compression(); buffer_compress_init_send(level); buffer_compress_init_recv(); } /* * Causes any further packets to be encrypted using the given key. The same * key is used for both sending and reception. However, both directions are * encrypted independently of each other. */ void packet_set_encryption_key(const u_char *key, u_int keylen, int number) { Cipher *cipher = cipher_by_number(number); if (cipher == NULL) fatal("packet_set_encryption_key: unknown cipher number %d", number); if (keylen < 20) fatal("packet_set_encryption_key: keylen too small: %d", keylen); if (keylen > SSH_SESSION_KEY_LENGTH) fatal("packet_set_encryption_key: keylen too big: %d", keylen); memcpy(active_state->ssh1_key, key, keylen); active_state->ssh1_keylen = keylen; cipher_init(&active_state->send_context, cipher, key, keylen, NULL, 0, CIPHER_ENCRYPT); cipher_init(&active_state->receive_context, cipher, key, keylen, NULL, 0, CIPHER_DECRYPT); } u_int packet_get_encryption_key(u_char *key) { if (key == NULL) return (active_state->ssh1_keylen); memcpy(key, active_state->ssh1_key, active_state->ssh1_keylen); return (active_state->ssh1_keylen); } /* Start constructing a packet to send. */ void packet_start(u_char type) { u_char buf[9]; int len; DBG(debug("packet_start[%d]", type)); len = compat20 ? 6 : 9; memset(buf, 0, len - 1); buf[len - 1] = type; buffer_clear(&active_state->outgoing_packet); buffer_append(&active_state->outgoing_packet, buf, len); } /* Append payload. */ void packet_put_char(int value) { char ch = value; buffer_append(&active_state->outgoing_packet, &ch, 1); } void packet_put_int(u_int value) { buffer_put_int(&active_state->outgoing_packet, value); } void packet_put_int64(u_int64_t value) { buffer_put_int64(&active_state->outgoing_packet, value); } void packet_put_string(const void *buf, u_int len) { buffer_put_string(&active_state->outgoing_packet, buf, len); } void packet_put_cstring(const char *str) { buffer_put_cstring(&active_state->outgoing_packet, str); } void packet_put_raw(const void *buf, u_int len) { buffer_append(&active_state->outgoing_packet, buf, len); } void packet_put_bignum(BIGNUM * value) { buffer_put_bignum(&active_state->outgoing_packet, value); } void packet_put_bignum2(BIGNUM * value) { buffer_put_bignum2(&active_state->outgoing_packet, value); } #ifdef OPENSSL_HAS_ECC void packet_put_ecpoint(const EC_GROUP *curve, const EC_POINT *point) { buffer_put_ecpoint(&active_state->outgoing_packet, curve, point); } #endif /* * Finalizes and sends the packet. If the encryption key has been set, * encrypts the packet before sending. */ static void packet_send1(void) { u_char buf[8], *cp; int i, padding, len; u_int checksum; u_int32_t rnd = 0; /* * If using packet compression, compress the payload of the outgoing * packet. */ if (active_state->packet_compression) { buffer_clear(&active_state->compression_buffer); /* Skip padding. */ buffer_consume(&active_state->outgoing_packet, 8); /* padding */ buffer_append(&active_state->compression_buffer, "\0\0\0\0\0\0\0\0", 8); buffer_compress(&active_state->outgoing_packet, &active_state->compression_buffer); buffer_clear(&active_state->outgoing_packet); buffer_append(&active_state->outgoing_packet, buffer_ptr(&active_state->compression_buffer), buffer_len(&active_state->compression_buffer)); } /* Compute packet length without padding (add checksum, remove padding). */ len = buffer_len(&active_state->outgoing_packet) + 4 - 8; /* Insert padding. Initialized to zero in packet_start1() */ padding = 8 - len % 8; if (!active_state->send_context.plaintext) { cp = buffer_ptr(&active_state->outgoing_packet); for (i = 0; i < padding; i++) { if (i % 4 == 0) rnd = arc4random(); cp[7 - i] = rnd & 0xff; rnd >>= 8; } } buffer_consume(&active_state->outgoing_packet, 8 - padding); /* Add check bytes. */ checksum = ssh_crc32(buffer_ptr(&active_state->outgoing_packet), buffer_len(&active_state->outgoing_packet)); put_u32(buf, checksum); buffer_append(&active_state->outgoing_packet, buf, 4); #ifdef PACKET_DEBUG fprintf(stderr, "packet_send plain: "); buffer_dump(&active_state->outgoing_packet); #endif /* Append to output. */ put_u32(buf, len); buffer_append(&active_state->output, buf, 4); cp = buffer_append_space(&active_state->output, buffer_len(&active_state->outgoing_packet)); cipher_crypt(&active_state->send_context, cp, buffer_ptr(&active_state->outgoing_packet), buffer_len(&active_state->outgoing_packet)); #ifdef PACKET_DEBUG fprintf(stderr, "encrypted: "); buffer_dump(&active_state->output); #endif active_state->p_send.packets++; active_state->p_send.bytes += len + buffer_len(&active_state->outgoing_packet); buffer_clear(&active_state->outgoing_packet); /* * Note that the packet is now only buffered in output. It won't be * actually sent until packet_write_wait or packet_write_poll is * called. */ } void set_newkeys(int mode) { Enc *enc; Mac *mac; Comp *comp; CipherContext *cc; u_int64_t *max_blocks; int crypt_type; debug2("set_newkeys: mode %d", mode); if (mode == MODE_OUT) { cc = &active_state->send_context; crypt_type = CIPHER_ENCRYPT; active_state->p_send.packets = active_state->p_send.blocks = 0; max_blocks = &active_state->max_blocks_out; } else { cc = &active_state->receive_context; crypt_type = CIPHER_DECRYPT; active_state->p_read.packets = active_state->p_read.blocks = 0; max_blocks = &active_state->max_blocks_in; } if (active_state->newkeys[mode] != NULL) { debug("set_newkeys: rekeying"); cipher_cleanup(cc); enc = &active_state->newkeys[mode]->enc; mac = &active_state->newkeys[mode]->mac; comp = &active_state->newkeys[mode]->comp; mac_clear(mac); xfree(enc->name); xfree(enc->iv); xfree(enc->key); xfree(mac->name); xfree(mac->key); xfree(comp->name); xfree(active_state->newkeys[mode]); } active_state->newkeys[mode] = kex_get_newkeys(mode); if (active_state->newkeys[mode] == NULL) fatal("newkeys: no keys for mode %d", mode); enc = &active_state->newkeys[mode]->enc; mac = &active_state->newkeys[mode]->mac; comp = &active_state->newkeys[mode]->comp; if (mac_init(mac) == 0) mac->enabled = 1; DBG(debug("cipher_init_context: %d", mode)); cipher_init(cc, enc->cipher, enc->key, enc->key_len, enc->iv, enc->block_size, crypt_type); /* Deleting the keys does not gain extra security */ /* memset(enc->iv, 0, enc->block_size); memset(enc->key, 0, enc->key_len); memset(mac->key, 0, mac->key_len); */ if ((comp->type == COMP_ZLIB || (comp->type == COMP_DELAYED && active_state->after_authentication)) && comp->enabled == 0) { packet_init_compression(); if (mode == MODE_OUT) buffer_compress_init_send(6); else buffer_compress_init_recv(); comp->enabled = 1; } /* * The 2^(blocksize*2) limit is too expensive for 3DES, * blowfish, etc, so enforce a 1GB limit for small blocksizes. */ if (enc->block_size >= 16) *max_blocks = (u_int64_t)1 << (enc->block_size*2); else *max_blocks = ((u_int64_t)1 << 30) / enc->block_size; if (active_state->rekey_limit) *max_blocks = MIN(*max_blocks, active_state->rekey_limit / enc->block_size); } /* * Delayed compression for SSH2 is enabled after authentication: * This happens on the server side after a SSH2_MSG_USERAUTH_SUCCESS is sent, * and on the client side after a SSH2_MSG_USERAUTH_SUCCESS is received. */ static void packet_enable_delayed_compress(void) { Comp *comp = NULL; int mode; /* * Remember that we are past the authentication step, so rekeying * with COMP_DELAYED will turn on compression immediately. */ active_state->after_authentication = 1; for (mode = 0; mode < MODE_MAX; mode++) { /* protocol error: USERAUTH_SUCCESS received before NEWKEYS */ if (active_state->newkeys[mode] == NULL) continue; comp = &active_state->newkeys[mode]->comp; if (comp && !comp->enabled && comp->type == COMP_DELAYED) { packet_init_compression(); if (mode == MODE_OUT) buffer_compress_init_send(6); else buffer_compress_init_recv(); comp->enabled = 1; } } } /* * Finalize packet in SSH2 format (compress, mac, encrypt, enqueue) */ static void packet_send2_wrapped(void) { u_char type, *cp, *macbuf = NULL; u_char padlen, pad; u_int packet_length = 0; u_int i, len; u_int32_t rnd = 0; Enc *enc = NULL; Mac *mac = NULL; Comp *comp = NULL; int block_size; if (active_state->newkeys[MODE_OUT] != NULL) { enc = &active_state->newkeys[MODE_OUT]->enc; mac = &active_state->newkeys[MODE_OUT]->mac; comp = &active_state->newkeys[MODE_OUT]->comp; } block_size = enc ? enc->block_size : 8; cp = buffer_ptr(&active_state->outgoing_packet); type = cp[5]; #ifdef PACKET_DEBUG fprintf(stderr, "plain: "); buffer_dump(&active_state->outgoing_packet); #endif if (comp && comp->enabled) { len = buffer_len(&active_state->outgoing_packet); /* skip header, compress only payload */ buffer_consume(&active_state->outgoing_packet, 5); buffer_clear(&active_state->compression_buffer); buffer_compress(&active_state->outgoing_packet, &active_state->compression_buffer); buffer_clear(&active_state->outgoing_packet); buffer_append(&active_state->outgoing_packet, "\0\0\0\0\0", 5); buffer_append(&active_state->outgoing_packet, buffer_ptr(&active_state->compression_buffer), buffer_len(&active_state->compression_buffer)); DBG(debug("compression: raw %d compressed %d", len, buffer_len(&active_state->outgoing_packet))); } /* sizeof (packet_len + pad_len + payload) */ len = buffer_len(&active_state->outgoing_packet); /* * calc size of padding, alloc space, get random data, * minimum padding is 4 bytes */ padlen = block_size - (len % block_size); if (padlen < 4) padlen += block_size; if (active_state->extra_pad) { /* will wrap if extra_pad+padlen > 255 */ active_state->extra_pad = roundup(active_state->extra_pad, block_size); pad = active_state->extra_pad - ((len + padlen) % active_state->extra_pad); debug3("packet_send2: adding %d (len %d padlen %d extra_pad %d)", pad, len, padlen, active_state->extra_pad); padlen += pad; active_state->extra_pad = 0; } cp = buffer_append_space(&active_state->outgoing_packet, padlen); if (enc && !active_state->send_context.plaintext) { /* random padding */ for (i = 0; i < padlen; i++) { if (i % 4 == 0) rnd = arc4random(); cp[i] = rnd & 0xff; rnd >>= 8; } } else { /* clear padding */ memset(cp, 0, padlen); } /* packet_length includes payload, padding and padding length field */ packet_length = buffer_len(&active_state->outgoing_packet) - 4; cp = buffer_ptr(&active_state->outgoing_packet); put_u32(cp, packet_length); cp[4] = padlen; DBG(debug("send: len %d (includes padlen %d)", packet_length+4, padlen)); /* compute MAC over seqnr and packet(length fields, payload, padding) */ if (mac && mac->enabled) { macbuf = mac_compute(mac, active_state->p_send.seqnr, buffer_ptr(&active_state->outgoing_packet), buffer_len(&active_state->outgoing_packet)); DBG(debug("done calc MAC out #%d", active_state->p_send.seqnr)); } /* encrypt packet and append to output buffer. */ cp = buffer_append_space(&active_state->output, buffer_len(&active_state->outgoing_packet)); cipher_crypt(&active_state->send_context, cp, buffer_ptr(&active_state->outgoing_packet), buffer_len(&active_state->outgoing_packet)); /* append unencrypted MAC */ if (mac && mac->enabled) buffer_append(&active_state->output, macbuf, mac->mac_len); #ifdef PACKET_DEBUG fprintf(stderr, "encrypted: "); buffer_dump(&active_state->output); #endif /* increment sequence number for outgoing packets */ if (++active_state->p_send.seqnr == 0) logit("outgoing seqnr wraps around"); if (++active_state->p_send.packets == 0) if (!(datafellows & SSH_BUG_NOREKEY)) fatal("XXX too many packets with same key"); active_state->p_send.blocks += (packet_length + 4) / block_size; active_state->p_send.bytes += packet_length + 4; buffer_clear(&active_state->outgoing_packet); if (type == SSH2_MSG_NEWKEYS) set_newkeys(MODE_OUT); else if (type == SSH2_MSG_USERAUTH_SUCCESS && active_state->server_side) packet_enable_delayed_compress(); } static void packet_send2(void) { struct packet *p; u_char type, *cp; cp = buffer_ptr(&active_state->outgoing_packet); type = cp[5]; /* during rekeying we can only send key exchange messages */ if (active_state->rekeying) { if (!((type >= SSH2_MSG_TRANSPORT_MIN) && (type <= SSH2_MSG_TRANSPORT_MAX))) { debug("enqueue packet: %u", type); p = xmalloc(sizeof(*p)); p->type = type; memcpy(&p->payload, &active_state->outgoing_packet, sizeof(Buffer)); buffer_init(&active_state->outgoing_packet); TAILQ_INSERT_TAIL(&active_state->outgoing, p, next); return; } } /* rekeying starts with sending KEXINIT */ if (type == SSH2_MSG_KEXINIT) active_state->rekeying = 1; packet_send2_wrapped(); /* after a NEWKEYS message we can send the complete queue */ if (type == SSH2_MSG_NEWKEYS) { active_state->rekeying = 0; while ((p = TAILQ_FIRST(&active_state->outgoing))) { type = p->type; debug("dequeue packet: %u", type); buffer_free(&active_state->outgoing_packet); memcpy(&active_state->outgoing_packet, &p->payload, sizeof(Buffer)); TAILQ_REMOVE(&active_state->outgoing, p, next); xfree(p); packet_send2_wrapped(); } } } void packet_send(void) { if (compat20) packet_send2(); else packet_send1(); DBG(debug("packet_send done")); } /* * Waits until a packet has been received, and returns its type. Note that * no other data is processed until this returns, so this function should not * be used during the interactive session. */ int packet_read_seqnr(u_int32_t *seqnr_p) { int type, len, ret, ms_remain, cont; fd_set *setp; char buf[8192]; struct timeval timeout, start, *timeoutp = NULL; DBG(debug("packet_read()")); setp = (fd_set *)xcalloc(howmany(active_state->connection_in + 1, NFDBITS), sizeof(fd_mask)); /* Since we are blocking, ensure that all written packets have been sent. */ packet_write_wait(); /* Stay in the loop until we have received a complete packet. */ for (;;) { /* Try to read a packet from the buffer. */ type = packet_read_poll_seqnr(seqnr_p); if (!compat20 && ( type == SSH_SMSG_SUCCESS || type == SSH_SMSG_FAILURE || type == SSH_CMSG_EOF || type == SSH_CMSG_EXIT_CONFIRMATION)) packet_check_eom(); /* If we got a packet, return it. */ if (type != SSH_MSG_NONE) { xfree(setp); return type; } /* * Otherwise, wait for some data to arrive, add it to the * buffer, and try again. */ memset(setp, 0, howmany(active_state->connection_in + 1, NFDBITS) * sizeof(fd_mask)); FD_SET(active_state->connection_in, setp); if (active_state->packet_timeout_ms > 0) { ms_remain = active_state->packet_timeout_ms; timeoutp = &timeout; } /* Wait for some data to arrive. */ for (;;) { if (active_state->packet_timeout_ms != -1) { ms_to_timeval(&timeout, ms_remain); gettimeofday(&start, NULL); } if ((ret = select(active_state->connection_in + 1, setp, NULL, NULL, timeoutp)) >= 0) break; if (errno != EAGAIN && errno != EINTR && errno != EWOULDBLOCK) break; if (active_state->packet_timeout_ms == -1) continue; ms_subtract_diff(&start, &ms_remain); if (ms_remain <= 0) { ret = 0; break; } } if (ret == 0) { logit("Connection to %.200s timed out while " "waiting to read", get_remote_ipaddr()); cleanup_exit(255); } /* Read data from the socket. */ do { cont = 0; len = roaming_read(active_state->connection_in, buf, sizeof(buf), &cont); } while (len == 0 && cont); if (len == 0) { logit("Connection closed by %.200s", get_remote_ipaddr()); cleanup_exit(255); } if (len < 0) fatal("Read from socket failed: %.100s", strerror(errno)); /* Append it to the buffer. */ packet_process_incoming(buf, len); } /* NOTREACHED */ } int packet_read(void) { return packet_read_seqnr(NULL); } /* * Waits until a packet has been received, verifies that its type matches * that given, and gives a fatal error and exits if there is a mismatch. */ void packet_read_expect(int expected_type) { int type; type = packet_read(); if (type != expected_type) packet_disconnect("Protocol error: expected packet type %d, got %d", expected_type, type); } /* Checks if a full packet is available in the data received so far via * packet_process_incoming. If so, reads the packet; otherwise returns * SSH_MSG_NONE. This does not wait for data from the connection. * * SSH_MSG_DISCONNECT is handled specially here. Also, * SSH_MSG_IGNORE messages are skipped by this function and are never returned * to higher levels. */ static int packet_read_poll1(void) { u_int len, padded_len; u_char *cp, type; u_int checksum, stored_checksum; /* Check if input size is less than minimum packet size. */ if (buffer_len(&active_state->input) < 4 + 8) return SSH_MSG_NONE; /* Get length of incoming packet. */ cp = buffer_ptr(&active_state->input); len = get_u32(cp); if (len < 1 + 2 + 2 || len > 256 * 1024) packet_disconnect("Bad packet length %u.", len); padded_len = (len + 8) & ~7; /* Check if the packet has been entirely received. */ if (buffer_len(&active_state->input) < 4 + padded_len) return SSH_MSG_NONE; /* The entire packet is in buffer. */ /* Consume packet length. */ buffer_consume(&active_state->input, 4); /* * Cryptographic attack detector for ssh * (C)1998 CORE-SDI, Buenos Aires Argentina * Ariel Futoransky(futo@core-sdi.com) */ if (!active_state->receive_context.plaintext) { switch (detect_attack(buffer_ptr(&active_state->input), padded_len)) { case DEATTACK_DETECTED: packet_disconnect("crc32 compensation attack: " "network attack detected"); case DEATTACK_DOS_DETECTED: packet_disconnect("deattack denial of " "service detected"); } } /* Decrypt data to incoming_packet. */ buffer_clear(&active_state->incoming_packet); cp = buffer_append_space(&active_state->incoming_packet, padded_len); cipher_crypt(&active_state->receive_context, cp, buffer_ptr(&active_state->input), padded_len); buffer_consume(&active_state->input, padded_len); #ifdef PACKET_DEBUG fprintf(stderr, "read_poll plain: "); buffer_dump(&active_state->incoming_packet); #endif /* Compute packet checksum. */ checksum = ssh_crc32(buffer_ptr(&active_state->incoming_packet), buffer_len(&active_state->incoming_packet) - 4); /* Skip padding. */ buffer_consume(&active_state->incoming_packet, 8 - len % 8); /* Test check bytes. */ if (len != buffer_len(&active_state->incoming_packet)) packet_disconnect("packet_read_poll1: len %d != buffer_len %d.", len, buffer_len(&active_state->incoming_packet)); cp = (u_char *)buffer_ptr(&active_state->incoming_packet) + len - 4; stored_checksum = get_u32(cp); if (checksum != stored_checksum) packet_disconnect("Corrupted check bytes on input."); buffer_consume_end(&active_state->incoming_packet, 4); if (active_state->packet_compression) { buffer_clear(&active_state->compression_buffer); buffer_uncompress(&active_state->incoming_packet, &active_state->compression_buffer); buffer_clear(&active_state->incoming_packet); buffer_append(&active_state->incoming_packet, buffer_ptr(&active_state->compression_buffer), buffer_len(&active_state->compression_buffer)); } active_state->p_read.packets++; active_state->p_read.bytes += padded_len + 4; type = buffer_get_char(&active_state->incoming_packet); if (type < SSH_MSG_MIN || type > SSH_MSG_MAX) packet_disconnect("Invalid ssh1 packet type: %d", type); return type; } static int packet_read_poll2(u_int32_t *seqnr_p) { u_int padlen, need; u_char *macbuf, *cp, type; u_int maclen, block_size; Enc *enc = NULL; Mac *mac = NULL; Comp *comp = NULL; if (active_state->packet_discard) return SSH_MSG_NONE; if (active_state->newkeys[MODE_IN] != NULL) { enc = &active_state->newkeys[MODE_IN]->enc; mac = &active_state->newkeys[MODE_IN]->mac; comp = &active_state->newkeys[MODE_IN]->comp; } maclen = mac && mac->enabled ? mac->mac_len : 0; block_size = enc ? enc->block_size : 8; if (active_state->packlen == 0) { /* * check if input size is less than the cipher block size, * decrypt first block and extract length of incoming packet */ if (buffer_len(&active_state->input) < block_size) return SSH_MSG_NONE; buffer_clear(&active_state->incoming_packet); cp = buffer_append_space(&active_state->incoming_packet, block_size); cipher_crypt(&active_state->receive_context, cp, buffer_ptr(&active_state->input), block_size); cp = buffer_ptr(&active_state->incoming_packet); active_state->packlen = get_u32(cp); if (active_state->packlen < 1 + 4 || active_state->packlen > PACKET_MAX_SIZE) { #ifdef PACKET_DEBUG buffer_dump(&active_state->incoming_packet); #endif logit("Bad packet length %u.", active_state->packlen); packet_start_discard(enc, mac, active_state->packlen, PACKET_MAX_SIZE); return SSH_MSG_NONE; } DBG(debug("input: packet len %u", active_state->packlen+4)); buffer_consume(&active_state->input, block_size); } /* we have a partial packet of block_size bytes */ need = 4 + active_state->packlen - block_size; DBG(debug("partial packet %d, need %d, maclen %d", block_size, need, maclen)); if (need % block_size != 0) { logit("padding error: need %d block %d mod %d", need, block_size, need % block_size); packet_start_discard(enc, mac, active_state->packlen, PACKET_MAX_SIZE - block_size); return SSH_MSG_NONE; } /* * check if the entire packet has been received and * decrypt into incoming_packet */ if (buffer_len(&active_state->input) < need + maclen) return SSH_MSG_NONE; #ifdef PACKET_DEBUG fprintf(stderr, "read_poll enc/full: "); buffer_dump(&active_state->input); #endif cp = buffer_append_space(&active_state->incoming_packet, need); cipher_crypt(&active_state->receive_context, cp, buffer_ptr(&active_state->input), need); buffer_consume(&active_state->input, need); /* * compute MAC over seqnr and packet, * increment sequence number for incoming packet */ if (mac && mac->enabled) { macbuf = mac_compute(mac, active_state->p_read.seqnr, buffer_ptr(&active_state->incoming_packet), buffer_len(&active_state->incoming_packet)); if (timingsafe_bcmp(macbuf, buffer_ptr(&active_state->input), mac->mac_len) != 0) { logit("Corrupted MAC on input."); if (need > PACKET_MAX_SIZE) fatal("internal error need %d", need); packet_start_discard(enc, mac, active_state->packlen, PACKET_MAX_SIZE - need); return SSH_MSG_NONE; } DBG(debug("MAC #%d ok", active_state->p_read.seqnr)); buffer_consume(&active_state->input, mac->mac_len); } /* XXX now it's safe to use fatal/packet_disconnect */ if (seqnr_p != NULL) *seqnr_p = active_state->p_read.seqnr; if (++active_state->p_read.seqnr == 0) logit("incoming seqnr wraps around"); if (++active_state->p_read.packets == 0) if (!(datafellows & SSH_BUG_NOREKEY)) fatal("XXX too many packets with same key"); active_state->p_read.blocks += (active_state->packlen + 4) / block_size; active_state->p_read.bytes += active_state->packlen + 4; /* get padlen */ cp = buffer_ptr(&active_state->incoming_packet); padlen = cp[4]; DBG(debug("input: padlen %d", padlen)); if (padlen < 4) packet_disconnect("Corrupted padlen %d on input.", padlen); /* skip packet size + padlen, discard padding */ buffer_consume(&active_state->incoming_packet, 4 + 1); buffer_consume_end(&active_state->incoming_packet, padlen); DBG(debug("input: len before de-compress %d", buffer_len(&active_state->incoming_packet))); if (comp && comp->enabled) { buffer_clear(&active_state->compression_buffer); buffer_uncompress(&active_state->incoming_packet, &active_state->compression_buffer); buffer_clear(&active_state->incoming_packet); buffer_append(&active_state->incoming_packet, buffer_ptr(&active_state->compression_buffer), buffer_len(&active_state->compression_buffer)); DBG(debug("input: len after de-compress %d", buffer_len(&active_state->incoming_packet))); } /* * get packet type, implies consume. * return length of payload (without type field) */ type = buffer_get_char(&active_state->incoming_packet); if (type < SSH2_MSG_MIN || type >= SSH2_MSG_LOCAL_MIN) packet_disconnect("Invalid ssh2 packet type: %d", type); if (type == SSH2_MSG_NEWKEYS) set_newkeys(MODE_IN); else if (type == SSH2_MSG_USERAUTH_SUCCESS && !active_state->server_side) packet_enable_delayed_compress(); #ifdef PACKET_DEBUG fprintf(stderr, "read/plain[%d]:\r\n", type); buffer_dump(&active_state->incoming_packet); #endif /* reset for next packet */ active_state->packlen = 0; return type; } int packet_read_poll_seqnr(u_int32_t *seqnr_p) { u_int reason, seqnr; u_char type; char *msg; for (;;) { if (compat20) { type = packet_read_poll2(seqnr_p); if (type) { active_state->keep_alive_timeouts = 0; DBG(debug("received packet type %d", type)); } switch (type) { case SSH2_MSG_IGNORE: debug3("Received SSH2_MSG_IGNORE"); break; case SSH2_MSG_DEBUG: packet_get_char(); msg = packet_get_string(NULL); debug("Remote: %.900s", msg); xfree(msg); msg = packet_get_string(NULL); xfree(msg); break; case SSH2_MSG_DISCONNECT: reason = packet_get_int(); msg = packet_get_string(NULL); logit("Received disconnect from %s: %u: %.400s", get_remote_ipaddr(), reason, msg); xfree(msg); cleanup_exit(255); break; case SSH2_MSG_UNIMPLEMENTED: seqnr = packet_get_int(); debug("Received SSH2_MSG_UNIMPLEMENTED for %u", seqnr); break; default: return type; } } else { type = packet_read_poll1(); switch (type) { case SSH_MSG_IGNORE: break; case SSH_MSG_DEBUG: msg = packet_get_string(NULL); debug("Remote: %.900s", msg); xfree(msg); break; case SSH_MSG_DISCONNECT: msg = packet_get_string(NULL); logit("Received disconnect from %s: %.400s", get_remote_ipaddr(), msg); cleanup_exit(255); break; default: if (type) DBG(debug("received packet type %d", type)); return type; } } } } int packet_read_poll(void) { return packet_read_poll_seqnr(NULL); } /* * Buffers the given amount of input characters. This is intended to be used * together with packet_read_poll. */ void packet_process_incoming(const char *buf, u_int len) { if (active_state->packet_discard) { active_state->keep_alive_timeouts = 0; /* ?? */ if (len >= active_state->packet_discard) packet_stop_discard(); active_state->packet_discard -= len; return; } buffer_append(&active_state->input, buf, len); } /* Returns a character from the packet. */ u_int packet_get_char(void) { char ch; buffer_get(&active_state->incoming_packet, &ch, 1); return (u_char) ch; } /* Returns an integer from the packet data. */ u_int packet_get_int(void) { return buffer_get_int(&active_state->incoming_packet); } /* Returns an 64 bit integer from the packet data. */ u_int64_t packet_get_int64(void) { return buffer_get_int64(&active_state->incoming_packet); } /* * Returns an arbitrary precision integer from the packet data. The integer * must have been initialized before this call. */ void packet_get_bignum(BIGNUM * value) { buffer_get_bignum(&active_state->incoming_packet, value); } void packet_get_bignum2(BIGNUM * value) { buffer_get_bignum2(&active_state->incoming_packet, value); } #ifdef OPENSSL_HAS_ECC void packet_get_ecpoint(const EC_GROUP *curve, EC_POINT *point) { buffer_get_ecpoint(&active_state->incoming_packet, curve, point); } #endif void * packet_get_raw(u_int *length_ptr) { u_int bytes = buffer_len(&active_state->incoming_packet); if (length_ptr != NULL) *length_ptr = bytes; return buffer_ptr(&active_state->incoming_packet); } int packet_remaining(void) { return buffer_len(&active_state->incoming_packet); } /* * Returns a string from the packet data. The string is allocated using * xmalloc; it is the responsibility of the calling program to free it when * no longer needed. The length_ptr argument may be NULL, or point to an * integer into which the length of the string is stored. */ void * packet_get_string(u_int *length_ptr) { return buffer_get_string(&active_state->incoming_packet, length_ptr); } void * packet_get_string_ptr(u_int *length_ptr) { return buffer_get_string_ptr(&active_state->incoming_packet, length_ptr); } /* Ensures the returned string has no embedded \0 characters in it. */ char * packet_get_cstring(u_int *length_ptr) { return buffer_get_cstring(&active_state->incoming_packet, length_ptr); } /* * Sends a diagnostic message from the server to the client. This message * can be sent at any time (but not while constructing another message). The * message is printed immediately, but only if the client is being executed * in verbose mode. These messages are primarily intended to ease debugging * authentication problems. The length of the formatted message must not * exceed 1024 bytes. This will automatically call packet_write_wait. */ void packet_send_debug(const char *fmt,...) { char buf[1024]; va_list args; if (compat20 && (datafellows & SSH_BUG_DEBUG)) return; va_start(args, fmt); vsnprintf(buf, sizeof(buf), fmt, args); va_end(args); if (compat20) { packet_start(SSH2_MSG_DEBUG); packet_put_char(0); /* bool: always display */ packet_put_cstring(buf); packet_put_cstring(""); } else { packet_start(SSH_MSG_DEBUG); packet_put_cstring(buf); } packet_send(); packet_write_wait(); } /* * Logs the error plus constructs and sends a disconnect packet, closes the * connection, and exits. This function never returns. The error message * should not contain a newline. The length of the formatted message must * not exceed 1024 bytes. */ void packet_disconnect(const char *fmt,...) { char buf[1024]; va_list args; static int disconnecting = 0; if (disconnecting) /* Guard against recursive invocations. */ fatal("packet_disconnect called recursively."); disconnecting = 1; /* * Format the message. Note that the caller must make sure the * message is of limited size. */ va_start(args, fmt); vsnprintf(buf, sizeof(buf), fmt, args); va_end(args); /* Display the error locally */ logit("Disconnecting: %.100s", buf); /* Send the disconnect message to the other side, and wait for it to get sent. */ if (compat20) { packet_start(SSH2_MSG_DISCONNECT); packet_put_int(SSH2_DISCONNECT_PROTOCOL_ERROR); packet_put_cstring(buf); packet_put_cstring(""); } else { packet_start(SSH_MSG_DISCONNECT); packet_put_cstring(buf); } packet_send(); packet_write_wait(); /* Stop listening for connections. */ channel_close_all(); /* Close the connection. */ packet_close(); cleanup_exit(255); } /* Checks if there is any buffered output, and tries to write some of the output. */ void packet_write_poll(void) { int len = buffer_len(&active_state->output); int cont; if (len > 0) { cont = 0; len = roaming_write(active_state->connection_out, buffer_ptr(&active_state->output), len, &cont); if (len == -1) { if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) return; fatal("Write failed: %.100s", strerror(errno)); } if (len == 0 && !cont) fatal("Write connection closed"); buffer_consume(&active_state->output, len); } } /* * Calls packet_write_poll repeatedly until all pending output data has been * written. */ void packet_write_wait(void) { fd_set *setp; int ret, ms_remain; struct timeval start, timeout, *timeoutp = NULL; setp = (fd_set *)xcalloc(howmany(active_state->connection_out + 1, NFDBITS), sizeof(fd_mask)); packet_write_poll(); while (packet_have_data_to_write()) { memset(setp, 0, howmany(active_state->connection_out + 1, NFDBITS) * sizeof(fd_mask)); FD_SET(active_state->connection_out, setp); if (active_state->packet_timeout_ms > 0) { ms_remain = active_state->packet_timeout_ms; timeoutp = &timeout; } for (;;) { if (active_state->packet_timeout_ms != -1) { ms_to_timeval(&timeout, ms_remain); gettimeofday(&start, NULL); } if ((ret = select(active_state->connection_out + 1, NULL, setp, NULL, timeoutp)) >= 0) break; if (errno != EAGAIN && errno != EINTR && errno != EWOULDBLOCK) break; if (active_state->packet_timeout_ms == -1) continue; ms_subtract_diff(&start, &ms_remain); if (ms_remain <= 0) { ret = 0; break; } } if (ret == 0) { logit("Connection to %.200s timed out while " "waiting to write", get_remote_ipaddr()); cleanup_exit(255); } packet_write_poll(); } xfree(setp); } /* Returns true if there is buffered data to write to the connection. */ int packet_have_data_to_write(void) { return buffer_len(&active_state->output) != 0; } /* Returns true if there is not too much data to write to the connection. */ int packet_not_very_much_data_to_write(void) { if (active_state->interactive_mode) return buffer_len(&active_state->output) < 16384; else return buffer_len(&active_state->output) < 128 * 1024; } static void packet_set_tos(int tos) { #if defined(IP_TOS) && !defined(IP_TOS_IS_BROKEN) if (!packet_connection_is_on_socket() || !packet_connection_is_ipv4()) return; debug3("%s: set IP_TOS 0x%02x", __func__, tos); if (setsockopt(active_state->connection_in, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)) < 0) error("setsockopt IP_TOS %d: %.100s:", tos, strerror(errno)); #endif } /* Informs that the current session is interactive. Sets IP flags for that. */ void packet_set_interactive(int interactive, int qos_interactive, int qos_bulk) { if (active_state->set_interactive_called) return; active_state->set_interactive_called = 1; /* Record that we are in interactive mode. */ active_state->interactive_mode = interactive; /* Only set socket options if using a socket. */ if (!packet_connection_is_on_socket()) return; set_nodelay(active_state->connection_in); packet_set_tos(interactive ? qos_interactive : qos_bulk); } /* Returns true if the current connection is interactive. */ int packet_is_interactive(void) { return active_state->interactive_mode; } int packet_set_maxsize(u_int s) { if (active_state->set_maxsize_called) { logit("packet_set_maxsize: called twice: old %d new %d", active_state->max_packet_size, s); return -1; } if (s < 4 * 1024 || s > 1024 * 1024) { logit("packet_set_maxsize: bad size %d", s); return -1; } active_state->set_maxsize_called = 1; debug("packet_set_maxsize: setting to %d", s); active_state->max_packet_size = s; return s; } int packet_inc_alive_timeouts(void) { return ++active_state->keep_alive_timeouts; } void packet_set_alive_timeouts(int ka) { active_state->keep_alive_timeouts = ka; } u_int packet_get_maxsize(void) { return active_state->max_packet_size; } /* roundup current message to pad bytes */ void packet_add_padding(u_char pad) { active_state->extra_pad = pad; } /* * 9.2. Ignored Data Message * * byte SSH_MSG_IGNORE * string data * * All implementations MUST understand (and ignore) this message at any * time (after receiving the protocol version). No implementation is * required to send them. This message can be used as an additional * protection measure against advanced traffic analysis techniques. */ void packet_send_ignore(int nbytes) { u_int32_t rnd = 0; int i; packet_start(compat20 ? SSH2_MSG_IGNORE : SSH_MSG_IGNORE); packet_put_int(nbytes); for (i = 0; i < nbytes; i++) { if (i % 4 == 0) rnd = arc4random(); packet_put_char((u_char)rnd & 0xff); rnd >>= 8; } } +#ifdef NONE_CIPHER_ENABLED +void +packet_request_rekeying(void) +{ + rekey_requested = 1; +} +#endif + #define MAX_PACKETS (1U<<31) int packet_need_rekeying(void) { if (datafellows & SSH_BUG_NOREKEY) return 0; +#ifdef NONE_CIPHER_ENABLED + if (rekey_requested == 1) { + rekey_requested = 0; + return 1; + } +#endif return (active_state->p_send.packets > MAX_PACKETS) || (active_state->p_read.packets > MAX_PACKETS) || (active_state->max_blocks_out && (active_state->p_send.blocks > active_state->max_blocks_out)) || (active_state->max_blocks_in && (active_state->p_read.blocks > active_state->max_blocks_in)); } void packet_set_rekey_limit(u_int32_t bytes) { active_state->rekey_limit = bytes; } void packet_set_server(void) { active_state->server_side = 1; } void packet_set_authenticated(void) { active_state->after_authentication = 1; } void * packet_get_input(void) { return (void *)&active_state->input; } void * packet_get_output(void) { return (void *)&active_state->output; } void * packet_get_newkeys(int mode) { return (void *)active_state->newkeys[mode]; } /* * Save the state for the real connection, and use a separate state when * resuming a suspended connection. */ void packet_backup_state(void) { struct session_state *tmp; close(active_state->connection_in); active_state->connection_in = -1; close(active_state->connection_out); active_state->connection_out = -1; if (backup_state) tmp = backup_state; else tmp = alloc_session_state(); backup_state = active_state; active_state = tmp; } /* * Swap in the old state when resuming a connecion. */ void packet_restore_state(void) { struct session_state *tmp; void *buf; u_int len; tmp = backup_state; backup_state = active_state; active_state = tmp; active_state->connection_in = backup_state->connection_in; backup_state->connection_in = -1; active_state->connection_out = backup_state->connection_out; backup_state->connection_out = -1; len = buffer_len(&backup_state->input); if (len > 0) { buf = buffer_ptr(&backup_state->input); buffer_append(&active_state->input, buf, len); buffer_clear(&backup_state->input); add_recv_bytes(len); } } + +#ifdef NONE_CIPHER_ENABLED +int +packet_get_authentication_state(void) +{ + return (active_state->after_authentication); +} +#endif diff --git a/crypto/openssh/packet.h b/crypto/openssh/packet.h index d516aae8d491..16e33cdcd53f 100644 --- a/crypto/openssh/packet.h +++ b/crypto/openssh/packet.h @@ -1,128 +1,135 @@ /* $OpenBSD: packet.h,v 1.55 2010/11/13 23:27:50 djm Exp $ */ +/* $FreeBSD$ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved * Interface for the packet protocol functions. * * As far as I am concerned, the code I have written for this software * can be used freely for any purpose. Any derived versions of this * software must be clearly marked as such, and if the derived work is * incompatible with the protocol description in the RFC file, it must be * called by a name other than "ssh" or "Secure Shell". */ #ifndef PACKET_H #define PACKET_H #include #include #ifdef OPENSSL_HAS_ECC #include #endif void packet_set_connection(int, int); void packet_set_timeout(int, int); void packet_set_nonblocking(void); int packet_get_connection_in(void); int packet_get_connection_out(void); void packet_close(void); void packet_set_encryption_key(const u_char *, u_int, int); u_int packet_get_encryption_key(u_char *); void packet_set_protocol_flags(u_int); u_int packet_get_protocol_flags(void); void packet_start_compression(int); void packet_set_interactive(int, int, int); int packet_is_interactive(void); void packet_set_server(void); void packet_set_authenticated(void); +#ifdef NONE_CIPHER_ENABLED +int packet_get_authentication_state(void); +#endif void packet_start(u_char); void packet_put_char(int ch); void packet_put_int(u_int value); void packet_put_int64(u_int64_t value); void packet_put_bignum(BIGNUM * value); void packet_put_bignum2(BIGNUM * value); #ifdef OPENSSL_HAS_ECC void packet_put_ecpoint(const EC_GROUP *, const EC_POINT *); #endif void packet_put_string(const void *buf, u_int len); void packet_put_cstring(const char *str); void packet_put_raw(const void *buf, u_int len); void packet_send(void); int packet_read(void); void packet_read_expect(int type); int packet_read_poll(void); void packet_process_incoming(const char *buf, u_int len); int packet_read_seqnr(u_int32_t *seqnr_p); int packet_read_poll_seqnr(u_int32_t *seqnr_p); u_int packet_get_char(void); u_int packet_get_int(void); u_int64_t packet_get_int64(void); void packet_get_bignum(BIGNUM * value); void packet_get_bignum2(BIGNUM * value); #ifdef OPENSSL_HAS_ECC void packet_get_ecpoint(const EC_GROUP *, EC_POINT *); #endif void *packet_get_raw(u_int *length_ptr); void *packet_get_string(u_int *length_ptr); char *packet_get_cstring(u_int *length_ptr); void *packet_get_string_ptr(u_int *length_ptr); void packet_disconnect(const char *fmt,...) __attribute__((format(printf, 1, 2))); void packet_send_debug(const char *fmt,...) __attribute__((format(printf, 1, 2))); void set_newkeys(int mode); int packet_get_keyiv_len(int); void packet_get_keyiv(int, u_char *, u_int); int packet_get_keycontext(int, u_char *); void packet_set_keycontext(int, u_char *); void packet_get_state(int, u_int32_t *, u_int64_t *, u_int32_t *, u_int64_t *); void packet_set_state(int, u_int32_t, u_int64_t, u_int32_t, u_int64_t); int packet_get_ssh1_cipher(void); void packet_set_iv(int, u_char *); void *packet_get_newkeys(int); void packet_write_poll(void); void packet_write_wait(void); int packet_have_data_to_write(void); int packet_not_very_much_data_to_write(void); int packet_connection_is_on_socket(void); int packet_connection_is_ipv4(void); int packet_remaining(void); void packet_send_ignore(int); void packet_add_padding(u_char); void tty_make_modes(int, struct termios *); void tty_parse_modes(int, int *); void packet_set_alive_timeouts(int); int packet_inc_alive_timeouts(void); int packet_set_maxsize(u_int); u_int packet_get_maxsize(void); /* don't allow remaining bytes after the end of the message */ #define packet_check_eom() \ do { \ int _len = packet_remaining(); \ if (_len > 0) { \ logit("Packet integrity error (%d bytes remaining) at %s:%d", \ _len ,__FILE__, __LINE__); \ packet_disconnect("Packet integrity error."); \ } \ } while (0) int packet_need_rekeying(void); +#ifdef NONE_CIPHER_ENABLED +void packet_request_rekeying(void); +#endif void packet_set_rekey_limit(u_int32_t); void packet_backup_state(void); void packet_restore_state(void); void *packet_get_input(void); void *packet_get_output(void); #endif /* PACKET_H */ diff --git a/crypto/openssh/readconf.c b/crypto/openssh/readconf.c index 43779afe7915..17a93a641241 100644 --- a/crypto/openssh/readconf.c +++ b/crypto/openssh/readconf.c @@ -1,1446 +1,1538 @@ /* $OpenBSD: readconf.c,v 1.190 2010/11/13 23:27:50 djm Exp $ */ +/* $FreeBSD$ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved * Functions for reading the configuration files. * * As far as I am concerned, the code I have written for this software * can be used freely for any purpose. Any derived versions of this * software must be clearly marked as such, and if the derived work is * incompatible with the protocol description in the RFC file, it must be * called by a name other than "ssh" or "Secure Shell". */ #include "includes.h" __RCSID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "xmalloc.h" #include "ssh.h" #include "compat.h" #include "cipher.h" #include "pathnames.h" #include "log.h" #include "key.h" #include "readconf.h" #include "match.h" #include "misc.h" #include "buffer.h" #include "kex.h" #include "mac.h" #include "version.h" /* Format of the configuration file: # Configuration data is parsed as follows: # 1. command line options # 2. user-specific file # 3. system-wide file # Any configuration value is only changed the first time it is set. # Thus, host-specific definitions should be at the beginning of the # configuration file, and defaults at the end. # Host-specific declarations. These may override anything above. A single # host may match multiple declarations; these are processed in the order # that they are given in. Host *.ngs.fi ngs.fi User foo Host fake.com HostName another.host.name.real.org User blaah Port 34289 ForwardX11 no ForwardAgent no Host books.com RemoteForward 9999 shadows.cs.hut.fi:9999 Cipher 3des Host fascist.blob.com Port 23123 User tylonen PasswordAuthentication no Host puukko.hut.fi User t35124p ProxyCommand ssh-proxy %h %p Host *.fr PublicKeyAuthentication no Host *.su Cipher none PasswordAuthentication no Host vpn.fake.com Tunnel yes TunnelDevice 3 # Defaults for various options Host * ForwardAgent no ForwardX11 no PasswordAuthentication yes RSAAuthentication yes RhostsRSAAuthentication yes StrictHostKeyChecking yes TcpKeepAlive no IdentityFile ~/.ssh/identity Port 22 EscapeChar ~ */ /* Keyword tokens. */ typedef enum { oBadOption, oForwardAgent, oForwardX11, oForwardX11Trusted, oForwardX11Timeout, oGatewayPorts, oExitOnForwardFailure, oPasswordAuthentication, oRSAAuthentication, oChallengeResponseAuthentication, oXAuthLocation, oIdentityFile, oHostName, oPort, oCipher, oRemoteForward, oLocalForward, oUser, oHost, oEscapeChar, oRhostsRSAAuthentication, oProxyCommand, oGlobalKnownHostsFile, oUserKnownHostsFile, oConnectionAttempts, oBatchMode, oCheckHostIP, oStrictHostKeyChecking, oCompression, oCompressionLevel, oTCPKeepAlive, oNumberOfPasswordPrompts, oUsePrivilegedPort, oLogLevel, oCiphers, oProtocol, oMacs, oGlobalKnownHostsFile2, oUserKnownHostsFile2, oPubkeyAuthentication, oKbdInteractiveAuthentication, oKbdInteractiveDevices, oHostKeyAlias, oDynamicForward, oPreferredAuthentications, oHostbasedAuthentication, oHostKeyAlgorithms, oBindAddress, oPKCS11Provider, oClearAllForwardings, oNoHostAuthenticationForLocalhost, oEnableSSHKeysign, oRekeyLimit, oVerifyHostKeyDNS, oConnectTimeout, oAddressFamily, oGssAuthentication, oGssDelegateCreds, oServerAliveInterval, oServerAliveCountMax, oIdentitiesOnly, oSendEnv, oControlPath, oControlMaster, oControlPersist, oHashKnownHosts, oTunnel, oTunnelDevice, oLocalCommand, oPermitLocalCommand, oVisualHostKey, oUseRoaming, oZeroKnowledgePasswordAuthentication, oKexAlgorithms, oIPQoS, + oHPNDisabled, oHPNBufferSize, oTcpRcvBufPoll, oTcpRcvBuf, +#ifdef NONE_CIPHER_ENABLED + oNoneEnabled, oNoneSwitch, +#endif oVersionAddendum, oDeprecated, oUnsupported } OpCodes; /* Textual representations of the tokens. */ static struct { const char *name; OpCodes opcode; } keywords[] = { { "forwardagent", oForwardAgent }, { "forwardx11", oForwardX11 }, { "forwardx11trusted", oForwardX11Trusted }, { "forwardx11timeout", oForwardX11Timeout }, { "exitonforwardfailure", oExitOnForwardFailure }, { "xauthlocation", oXAuthLocation }, { "gatewayports", oGatewayPorts }, { "useprivilegedport", oUsePrivilegedPort }, { "rhostsauthentication", oDeprecated }, { "passwordauthentication", oPasswordAuthentication }, { "kbdinteractiveauthentication", oKbdInteractiveAuthentication }, { "kbdinteractivedevices", oKbdInteractiveDevices }, { "rsaauthentication", oRSAAuthentication }, { "pubkeyauthentication", oPubkeyAuthentication }, { "dsaauthentication", oPubkeyAuthentication }, /* alias */ { "rhostsrsaauthentication", oRhostsRSAAuthentication }, { "hostbasedauthentication", oHostbasedAuthentication }, { "challengeresponseauthentication", oChallengeResponseAuthentication }, { "skeyauthentication", oChallengeResponseAuthentication }, /* alias */ { "tisauthentication", oChallengeResponseAuthentication }, /* alias */ { "kerberosauthentication", oUnsupported }, { "kerberostgtpassing", oUnsupported }, { "afstokenpassing", oUnsupported }, #if defined(GSSAPI) { "gssapiauthentication", oGssAuthentication }, { "gssapidelegatecredentials", oGssDelegateCreds }, #else { "gssapiauthentication", oUnsupported }, { "gssapidelegatecredentials", oUnsupported }, #endif { "fallbacktorsh", oDeprecated }, { "usersh", oDeprecated }, { "identityfile", oIdentityFile }, { "identityfile2", oIdentityFile }, /* obsolete */ { "identitiesonly", oIdentitiesOnly }, { "hostname", oHostName }, { "hostkeyalias", oHostKeyAlias }, { "proxycommand", oProxyCommand }, { "port", oPort }, { "cipher", oCipher }, { "ciphers", oCiphers }, { "macs", oMacs }, { "protocol", oProtocol }, { "remoteforward", oRemoteForward }, { "localforward", oLocalForward }, { "user", oUser }, { "host", oHost }, { "escapechar", oEscapeChar }, { "globalknownhostsfile", oGlobalKnownHostsFile }, { "globalknownhostsfile2", oGlobalKnownHostsFile2 }, /* obsolete */ { "userknownhostsfile", oUserKnownHostsFile }, { "userknownhostsfile2", oUserKnownHostsFile2 }, /* obsolete */ { "connectionattempts", oConnectionAttempts }, { "batchmode", oBatchMode }, { "checkhostip", oCheckHostIP }, { "stricthostkeychecking", oStrictHostKeyChecking }, { "compression", oCompression }, { "compressionlevel", oCompressionLevel }, { "tcpkeepalive", oTCPKeepAlive }, { "keepalive", oTCPKeepAlive }, /* obsolete */ { "numberofpasswordprompts", oNumberOfPasswordPrompts }, { "loglevel", oLogLevel }, { "dynamicforward", oDynamicForward }, { "preferredauthentications", oPreferredAuthentications }, { "hostkeyalgorithms", oHostKeyAlgorithms }, { "bindaddress", oBindAddress }, #ifdef ENABLE_PKCS11 { "smartcarddevice", oPKCS11Provider }, { "pkcs11provider", oPKCS11Provider }, #else { "smartcarddevice", oUnsupported }, { "pkcs11provider", oUnsupported }, #endif { "clearallforwardings", oClearAllForwardings }, { "enablesshkeysign", oEnableSSHKeysign }, { "verifyhostkeydns", oVerifyHostKeyDNS }, { "nohostauthenticationforlocalhost", oNoHostAuthenticationForLocalhost }, { "rekeylimit", oRekeyLimit }, { "connecttimeout", oConnectTimeout }, { "addressfamily", oAddressFamily }, { "serveraliveinterval", oServerAliveInterval }, { "serveralivecountmax", oServerAliveCountMax }, { "sendenv", oSendEnv }, { "controlpath", oControlPath }, { "controlmaster", oControlMaster }, { "controlpersist", oControlPersist }, { "hashknownhosts", oHashKnownHosts }, { "tunnel", oTunnel }, { "tunneldevice", oTunnelDevice }, { "localcommand", oLocalCommand }, { "permitlocalcommand", oPermitLocalCommand }, { "visualhostkey", oVisualHostKey }, { "useroaming", oUseRoaming }, #ifdef JPAKE { "zeroknowledgepasswordauthentication", oZeroKnowledgePasswordAuthentication }, #else { "zeroknowledgepasswordauthentication", oUnsupported }, #endif { "kexalgorithms", oKexAlgorithms }, { "ipqos", oIPQoS }, + { "hpndisabled", oHPNDisabled }, + { "hpnbuffersize", oHPNBufferSize }, + { "tcprcvbufpoll", oTcpRcvBufPoll }, + { "tcprcvbuf", oTcpRcvBuf }, +#ifdef NONE_CIPHER_ENABLED + { "noneenabled", oNoneEnabled }, + { "noneswitch", oNoneSwitch }, +#endif { "versionaddendum", oVersionAddendum }, { NULL, oBadOption } }; /* * Adds a local TCP/IP port forward to options. Never returns if there is an * error. */ void add_local_forward(Options *options, const Forward *newfwd) { Forward *fwd; #ifndef NO_IPPORT_RESERVED_CONCEPT extern uid_t original_real_uid; int ipport_reserved; #ifdef __FreeBSD__ size_t len_ipport_reserved = sizeof(ipport_reserved); if (sysctlbyname("net.inet.ip.portrange.reservedhigh", &ipport_reserved, &len_ipport_reserved, NULL, 0) != 0) ipport_reserved = IPPORT_RESERVED; else ipport_reserved++; #else ipport_reserved = IPPORT_RESERVED; #endif if (newfwd->listen_port < ipport_reserved && original_real_uid != 0) fatal("Privileged ports can only be forwarded by root."); #endif options->local_forwards = xrealloc(options->local_forwards, options->num_local_forwards + 1, sizeof(*options->local_forwards)); fwd = &options->local_forwards[options->num_local_forwards++]; fwd->listen_host = newfwd->listen_host; fwd->listen_port = newfwd->listen_port; fwd->connect_host = newfwd->connect_host; fwd->connect_port = newfwd->connect_port; } /* * Adds a remote TCP/IP port forward to options. Never returns if there is * an error. */ void add_remote_forward(Options *options, const Forward *newfwd) { Forward *fwd; options->remote_forwards = xrealloc(options->remote_forwards, options->num_remote_forwards + 1, sizeof(*options->remote_forwards)); fwd = &options->remote_forwards[options->num_remote_forwards++]; fwd->listen_host = newfwd->listen_host; fwd->listen_port = newfwd->listen_port; fwd->connect_host = newfwd->connect_host; fwd->connect_port = newfwd->connect_port; fwd->allocated_port = 0; } static void clear_forwardings(Options *options) { int i; for (i = 0; i < options->num_local_forwards; i++) { if (options->local_forwards[i].listen_host != NULL) xfree(options->local_forwards[i].listen_host); xfree(options->local_forwards[i].connect_host); } if (options->num_local_forwards > 0) { xfree(options->local_forwards); options->local_forwards = NULL; } options->num_local_forwards = 0; for (i = 0; i < options->num_remote_forwards; i++) { if (options->remote_forwards[i].listen_host != NULL) xfree(options->remote_forwards[i].listen_host); xfree(options->remote_forwards[i].connect_host); } if (options->num_remote_forwards > 0) { xfree(options->remote_forwards); options->remote_forwards = NULL; } options->num_remote_forwards = 0; options->tun_open = SSH_TUNMODE_NO; } /* * Returns the number of the token pointed to by cp or oBadOption. */ static OpCodes parse_token(const char *cp, const char *filename, int linenum) { u_int i; for (i = 0; keywords[i].name; i++) if (strcasecmp(cp, keywords[i].name) == 0) return keywords[i].opcode; error("%s: line %d: Bad configuration option: %s", filename, linenum, cp); return oBadOption; } /* * Processes a single option line as used in the configuration files. This * only sets those values that have not already been set. */ #define WHITESPACE " \t\r\n" int process_config_line(Options *options, const char *host, char *line, const char *filename, int linenum, int *activep) { char *s, **charptr, *endofnumber, *keyword, *arg, *arg2, fwdarg[256]; int opcode, *intptr, value, value2, scale; LogLevel *log_level_ptr; long long orig, val64; size_t len; Forward fwd; /* Strip trailing whitespace */ for (len = strlen(line) - 1; len > 0; len--) { if (strchr(WHITESPACE, line[len]) == NULL) break; line[len] = '\0'; } s = line; /* Get the keyword. (Each line is supposed to begin with a keyword). */ if ((keyword = strdelim(&s)) == NULL) return 0; /* Ignore leading whitespace. */ if (*keyword == '\0') keyword = strdelim(&s); if (keyword == NULL || !*keyword || *keyword == '\n' || *keyword == '#') return 0; opcode = parse_token(keyword, filename, linenum); switch (opcode) { case oBadOption: /* don't panic, but count bad options */ return -1; /* NOTREACHED */ case oConnectTimeout: intptr = &options->connection_timeout; parse_time: arg = strdelim(&s); if (!arg || *arg == '\0') fatal("%s line %d: missing time value.", filename, linenum); if ((value = convtime(arg)) == -1) fatal("%s line %d: invalid time value.", filename, linenum); if (*activep && *intptr == -1) *intptr = value; break; case oForwardAgent: intptr = &options->forward_agent; parse_flag: arg = strdelim(&s); if (!arg || *arg == '\0') fatal("%.200s line %d: Missing yes/no argument.", filename, linenum); value = 0; /* To avoid compiler warning... */ if (strcmp(arg, "yes") == 0 || strcmp(arg, "true") == 0) value = 1; else if (strcmp(arg, "no") == 0 || strcmp(arg, "false") == 0) value = 0; else fatal("%.200s line %d: Bad yes/no argument.", filename, linenum); if (*activep && *intptr == -1) *intptr = value; break; case oForwardX11: intptr = &options->forward_x11; goto parse_flag; case oForwardX11Trusted: intptr = &options->forward_x11_trusted; goto parse_flag; case oForwardX11Timeout: intptr = &options->forward_x11_timeout; goto parse_time; case oGatewayPorts: intptr = &options->gateway_ports; goto parse_flag; case oExitOnForwardFailure: intptr = &options->exit_on_forward_failure; goto parse_flag; case oUsePrivilegedPort: intptr = &options->use_privileged_port; goto parse_flag; case oPasswordAuthentication: intptr = &options->password_authentication; goto parse_flag; case oZeroKnowledgePasswordAuthentication: intptr = &options->zero_knowledge_password_authentication; goto parse_flag; case oKbdInteractiveAuthentication: intptr = &options->kbd_interactive_authentication; goto parse_flag; case oKbdInteractiveDevices: charptr = &options->kbd_interactive_devices; goto parse_string; case oPubkeyAuthentication: intptr = &options->pubkey_authentication; goto parse_flag; case oRSAAuthentication: intptr = &options->rsa_authentication; goto parse_flag; case oRhostsRSAAuthentication: intptr = &options->rhosts_rsa_authentication; goto parse_flag; case oHostbasedAuthentication: intptr = &options->hostbased_authentication; goto parse_flag; case oChallengeResponseAuthentication: intptr = &options->challenge_response_authentication; goto parse_flag; case oGssAuthentication: intptr = &options->gss_authentication; goto parse_flag; case oGssDelegateCreds: intptr = &options->gss_deleg_creds; goto parse_flag; case oBatchMode: intptr = &options->batch_mode; goto parse_flag; case oCheckHostIP: intptr = &options->check_host_ip; goto parse_flag; case oVerifyHostKeyDNS: intptr = &options->verify_host_key_dns; goto parse_yesnoask; case oStrictHostKeyChecking: intptr = &options->strict_host_key_checking; parse_yesnoask: arg = strdelim(&s); if (!arg || *arg == '\0') fatal("%.200s line %d: Missing yes/no/ask argument.", filename, linenum); value = 0; /* To avoid compiler warning... */ if (strcmp(arg, "yes") == 0 || strcmp(arg, "true") == 0) value = 1; else if (strcmp(arg, "no") == 0 || strcmp(arg, "false") == 0) value = 0; else if (strcmp(arg, "ask") == 0) value = 2; else fatal("%.200s line %d: Bad yes/no/ask argument.", filename, linenum); if (*activep && *intptr == -1) *intptr = value; break; case oCompression: intptr = &options->compression; goto parse_flag; case oTCPKeepAlive: intptr = &options->tcp_keep_alive; goto parse_flag; case oNoHostAuthenticationForLocalhost: intptr = &options->no_host_authentication_for_localhost; goto parse_flag; case oNumberOfPasswordPrompts: intptr = &options->number_of_password_prompts; goto parse_int; case oCompressionLevel: intptr = &options->compression_level; goto parse_int; case oRekeyLimit: arg = strdelim(&s); if (!arg || *arg == '\0') fatal("%.200s line %d: Missing argument.", filename, linenum); if (arg[0] < '0' || arg[0] > '9') fatal("%.200s line %d: Bad number.", filename, linenum); orig = val64 = strtoll(arg, &endofnumber, 10); if (arg == endofnumber) fatal("%.200s line %d: Bad number.", filename, linenum); switch (toupper(*endofnumber)) { case '\0': scale = 1; break; case 'K': scale = 1<<10; break; case 'M': scale = 1<<20; break; case 'G': scale = 1<<30; break; default: fatal("%.200s line %d: Invalid RekeyLimit suffix", filename, linenum); } val64 *= scale; /* detect integer wrap and too-large limits */ if ((val64 / scale) != orig || val64 > UINT_MAX) fatal("%.200s line %d: RekeyLimit too large", filename, linenum); if (val64 < 16) fatal("%.200s line %d: RekeyLimit too small", filename, linenum); if (*activep && options->rekey_limit == -1) options->rekey_limit = (u_int32_t)val64; break; case oIdentityFile: arg = strdelim(&s); if (!arg || *arg == '\0') fatal("%.200s line %d: Missing argument.", filename, linenum); if (*activep) { intptr = &options->num_identity_files; if (*intptr >= SSH_MAX_IDENTITY_FILES) fatal("%.200s line %d: Too many identity files specified (max %d).", filename, linenum, SSH_MAX_IDENTITY_FILES); charptr = &options->identity_files[*intptr]; *charptr = xstrdup(arg); *intptr = *intptr + 1; } break; case oXAuthLocation: charptr=&options->xauth_location; goto parse_string; case oUser: charptr = &options->user; parse_string: arg = strdelim(&s); if (!arg || *arg == '\0') fatal("%.200s line %d: Missing argument.", filename, linenum); if (*activep && *charptr == NULL) *charptr = xstrdup(arg); break; case oGlobalKnownHostsFile: charptr = &options->system_hostfile; goto parse_string; case oUserKnownHostsFile: charptr = &options->user_hostfile; goto parse_string; case oGlobalKnownHostsFile2: charptr = &options->system_hostfile2; goto parse_string; case oUserKnownHostsFile2: charptr = &options->user_hostfile2; goto parse_string; case oHostName: charptr = &options->hostname; goto parse_string; case oHostKeyAlias: charptr = &options->host_key_alias; goto parse_string; case oPreferredAuthentications: charptr = &options->preferred_authentications; goto parse_string; case oBindAddress: charptr = &options->bind_address; goto parse_string; case oPKCS11Provider: charptr = &options->pkcs11_provider; goto parse_string; case oProxyCommand: charptr = &options->proxy_command; parse_command: if (s == NULL) fatal("%.200s line %d: Missing argument.", filename, linenum); len = strspn(s, WHITESPACE "="); if (*activep && *charptr == NULL) *charptr = xstrdup(s + len); return 0; case oPort: intptr = &options->port; parse_int: arg = strdelim(&s); if (!arg || *arg == '\0') fatal("%.200s line %d: Missing argument.", filename, linenum); if (arg[0] < '0' || arg[0] > '9') fatal("%.200s line %d: Bad number.", filename, linenum); /* Octal, decimal, or hex format? */ value = strtol(arg, &endofnumber, 0); if (arg == endofnumber) fatal("%.200s line %d: Bad number.", filename, linenum); if (*activep && *intptr == -1) *intptr = value; break; case oConnectionAttempts: intptr = &options->connection_attempts; goto parse_int; case oCipher: intptr = &options->cipher; arg = strdelim(&s); if (!arg || *arg == '\0') fatal("%.200s line %d: Missing argument.", filename, linenum); value = cipher_number(arg); if (value == -1) fatal("%.200s line %d: Bad cipher '%s'.", filename, linenum, arg ? arg : ""); if (*activep && *intptr == -1) *intptr = value; break; case oCiphers: arg = strdelim(&s); if (!arg || *arg == '\0') fatal("%.200s line %d: Missing argument.", filename, linenum); if (!ciphers_valid(arg)) fatal("%.200s line %d: Bad SSH2 cipher spec '%s'.", filename, linenum, arg ? arg : ""); if (*activep && options->ciphers == NULL) options->ciphers = xstrdup(arg); break; case oMacs: arg = strdelim(&s); if (!arg || *arg == '\0') fatal("%.200s line %d: Missing argument.", filename, linenum); if (!mac_valid(arg)) fatal("%.200s line %d: Bad SSH2 Mac spec '%s'.", filename, linenum, arg ? arg : ""); if (*activep && options->macs == NULL) options->macs = xstrdup(arg); break; case oKexAlgorithms: arg = strdelim(&s); if (!arg || *arg == '\0') fatal("%.200s line %d: Missing argument.", filename, linenum); if (!kex_names_valid(arg)) fatal("%.200s line %d: Bad SSH2 KexAlgorithms '%s'.", filename, linenum, arg ? arg : ""); if (*activep && options->kex_algorithms == NULL) options->kex_algorithms = xstrdup(arg); break; case oHostKeyAlgorithms: arg = strdelim(&s); if (!arg || *arg == '\0') fatal("%.200s line %d: Missing argument.", filename, linenum); if (!key_names_valid2(arg)) fatal("%.200s line %d: Bad protocol 2 host key algorithms '%s'.", filename, linenum, arg ? arg : ""); if (*activep && options->hostkeyalgorithms == NULL) options->hostkeyalgorithms = xstrdup(arg); break; case oProtocol: intptr = &options->protocol; arg = strdelim(&s); if (!arg || *arg == '\0') fatal("%.200s line %d: Missing argument.", filename, linenum); value = proto_spec(arg); if (value == SSH_PROTO_UNKNOWN) fatal("%.200s line %d: Bad protocol spec '%s'.", filename, linenum, arg ? arg : ""); if (*activep && *intptr == SSH_PROTO_UNKNOWN) *intptr = value; break; case oLogLevel: log_level_ptr = &options->log_level; arg = strdelim(&s); value = log_level_number(arg); if (value == SYSLOG_LEVEL_NOT_SET) fatal("%.200s line %d: unsupported log level '%s'", filename, linenum, arg ? arg : ""); if (*activep && *log_level_ptr == SYSLOG_LEVEL_NOT_SET) *log_level_ptr = (LogLevel) value; break; case oLocalForward: case oRemoteForward: case oDynamicForward: arg = strdelim(&s); if (arg == NULL || *arg == '\0') fatal("%.200s line %d: Missing port argument.", filename, linenum); if (opcode == oLocalForward || opcode == oRemoteForward) { arg2 = strdelim(&s); if (arg2 == NULL || *arg2 == '\0') fatal("%.200s line %d: Missing target argument.", filename, linenum); /* construct a string for parse_forward */ snprintf(fwdarg, sizeof(fwdarg), "%s:%s", arg, arg2); } else if (opcode == oDynamicForward) { strlcpy(fwdarg, arg, sizeof(fwdarg)); } if (parse_forward(&fwd, fwdarg, opcode == oDynamicForward ? 1 : 0, opcode == oRemoteForward ? 1 : 0) == 0) fatal("%.200s line %d: Bad forwarding specification.", filename, linenum); if (*activep) { if (opcode == oLocalForward || opcode == oDynamicForward) add_local_forward(options, &fwd); else if (opcode == oRemoteForward) add_remote_forward(options, &fwd); } break; case oClearAllForwardings: intptr = &options->clear_forwardings; goto parse_flag; case oHost: *activep = 0; while ((arg = strdelim(&s)) != NULL && *arg != '\0') if (match_pattern(host, arg)) { debug("Applying options for %.100s", arg); *activep = 1; break; } /* Avoid garbage check below, as strdelim is done. */ return 0; case oEscapeChar: intptr = &options->escape_char; arg = strdelim(&s); if (!arg || *arg == '\0') fatal("%.200s line %d: Missing argument.", filename, linenum); if (arg[0] == '^' && arg[2] == 0 && (u_char) arg[1] >= 64 && (u_char) arg[1] < 128) value = (u_char) arg[1] & 31; else if (strlen(arg) == 1) value = (u_char) arg[0]; else if (strcmp(arg, "none") == 0) value = SSH_ESCAPECHAR_NONE; else { fatal("%.200s line %d: Bad escape character.", filename, linenum); /* NOTREACHED */ value = 0; /* Avoid compiler warning. */ } if (*activep && *intptr == -1) *intptr = value; break; case oAddressFamily: arg = strdelim(&s); if (!arg || *arg == '\0') fatal("%s line %d: missing address family.", filename, linenum); intptr = &options->address_family; if (strcasecmp(arg, "inet") == 0) value = AF_INET; else if (strcasecmp(arg, "inet6") == 0) value = AF_INET6; else if (strcasecmp(arg, "any") == 0) value = AF_UNSPEC; else fatal("Unsupported AddressFamily \"%s\"", arg); if (*activep && *intptr == -1) *intptr = value; break; case oEnableSSHKeysign: intptr = &options->enable_ssh_keysign; goto parse_flag; case oIdentitiesOnly: intptr = &options->identities_only; goto parse_flag; case oServerAliveInterval: intptr = &options->server_alive_interval; goto parse_time; case oServerAliveCountMax: intptr = &options->server_alive_count_max; goto parse_int; case oSendEnv: while ((arg = strdelim(&s)) != NULL && *arg != '\0') { if (strchr(arg, '=') != NULL) fatal("%s line %d: Invalid environment name.", filename, linenum); if (!*activep) continue; if (options->num_send_env >= MAX_SEND_ENV) fatal("%s line %d: too many send env.", filename, linenum); options->send_env[options->num_send_env++] = xstrdup(arg); } break; case oControlPath: charptr = &options->control_path; goto parse_string; case oControlMaster: intptr = &options->control_master; arg = strdelim(&s); if (!arg || *arg == '\0') fatal("%.200s line %d: Missing ControlMaster argument.", filename, linenum); value = 0; /* To avoid compiler warning... */ if (strcmp(arg, "yes") == 0 || strcmp(arg, "true") == 0) value = SSHCTL_MASTER_YES; else if (strcmp(arg, "no") == 0 || strcmp(arg, "false") == 0) value = SSHCTL_MASTER_NO; else if (strcmp(arg, "auto") == 0) value = SSHCTL_MASTER_AUTO; else if (strcmp(arg, "ask") == 0) value = SSHCTL_MASTER_ASK; else if (strcmp(arg, "autoask") == 0) value = SSHCTL_MASTER_AUTO_ASK; else fatal("%.200s line %d: Bad ControlMaster argument.", filename, linenum); if (*activep && *intptr == -1) *intptr = value; break; case oControlPersist: /* no/false/yes/true, or a time spec */ intptr = &options->control_persist; arg = strdelim(&s); if (!arg || *arg == '\0') fatal("%.200s line %d: Missing ControlPersist" " argument.", filename, linenum); value = 0; value2 = 0; /* timeout */ if (strcmp(arg, "no") == 0 || strcmp(arg, "false") == 0) value = 0; else if (strcmp(arg, "yes") == 0 || strcmp(arg, "true") == 0) value = 1; else if ((value2 = convtime(arg)) >= 0) value = 1; else fatal("%.200s line %d: Bad ControlPersist argument.", filename, linenum); if (*activep && *intptr == -1) { *intptr = value; options->control_persist_timeout = value2; } break; case oHashKnownHosts: intptr = &options->hash_known_hosts; goto parse_flag; case oTunnel: intptr = &options->tun_open; arg = strdelim(&s); if (!arg || *arg == '\0') fatal("%s line %d: Missing yes/point-to-point/" "ethernet/no argument.", filename, linenum); value = 0; /* silence compiler */ if (strcasecmp(arg, "ethernet") == 0) value = SSH_TUNMODE_ETHERNET; else if (strcasecmp(arg, "point-to-point") == 0) value = SSH_TUNMODE_POINTOPOINT; else if (strcasecmp(arg, "yes") == 0) value = SSH_TUNMODE_DEFAULT; else if (strcasecmp(arg, "no") == 0) value = SSH_TUNMODE_NO; else fatal("%s line %d: Bad yes/point-to-point/ethernet/" "no argument: %s", filename, linenum, arg); if (*activep) *intptr = value; break; case oTunnelDevice: arg = strdelim(&s); if (!arg || *arg == '\0') fatal("%.200s line %d: Missing argument.", filename, linenum); value = a2tun(arg, &value2); if (value == SSH_TUNID_ERR) fatal("%.200s line %d: Bad tun device.", filename, linenum); if (*activep) { options->tun_local = value; options->tun_remote = value2; } break; case oLocalCommand: charptr = &options->local_command; goto parse_command; case oPermitLocalCommand: intptr = &options->permit_local_command; goto parse_flag; case oVisualHostKey: intptr = &options->visual_host_key; goto parse_flag; case oIPQoS: arg = strdelim(&s); if ((value = parse_ipqos(arg)) == -1) fatal("%s line %d: Bad IPQoS value: %s", filename, linenum, arg); arg = strdelim(&s); if (arg == NULL) value2 = value; else if ((value2 = parse_ipqos(arg)) == -1) fatal("%s line %d: Bad IPQoS value: %s", filename, linenum, arg); if (*activep) { options->ip_qos_interactive = value; options->ip_qos_bulk = value2; } break; case oUseRoaming: intptr = &options->use_roaming; goto parse_flag; case oVersionAddendum: ssh_version_set_addendum(strtok(s, "\n")); do { arg = strdelim(&s); } while (arg != NULL && *arg != '\0'); break; + case oHPNDisabled: + intptr = &options->hpn_disabled; + goto parse_flag; + + case oHPNBufferSize: + intptr = &options->hpn_buffer_size; + goto parse_int; + + case oTcpRcvBufPoll: + intptr = &options->tcp_rcv_buf_poll; + goto parse_flag; + + case oTcpRcvBuf: + intptr = &options->tcp_rcv_buf; + goto parse_int; + +#ifdef NONE_CIPHER_ENABLED + case oNoneEnabled: + intptr = &options->none_enabled; + goto parse_flag; + + /* + * We check to see if the command comes from the command line or not. + * If it does then enable it otherwise fail. NONE must never be a + * default configuration. + */ + case oNoneSwitch: + if (strcmp(filename,"command-line") == 0) { + intptr = &options->none_switch; + goto parse_flag; + } else { + debug("NoneSwitch directive found in %.200s.", + filename); + error("NoneSwitch is found in %.200s.\n" + "You may only use this configuration option " + "from the command line", filename); + error("Continuing..."); + return 0; + } +#endif + case oDeprecated: debug("%s line %d: Deprecated option \"%s\"", filename, linenum, keyword); return 0; case oUnsupported: error("%s line %d: Unsupported option \"%s\"", filename, linenum, keyword); return 0; default: fatal("process_config_line: Unimplemented opcode %d", opcode); } /* Check that there is no garbage at end of line. */ if ((arg = strdelim(&s)) != NULL && *arg != '\0') { fatal("%.200s line %d: garbage at end of line; \"%.200s\".", filename, linenum, arg); } return 0; } /* * Reads the config file and modifies the options accordingly. Options * should already be initialized before this call. This never returns if * there is an error. If the file does not exist, this returns 0. */ int read_config_file(const char *filename, const char *host, Options *options, int checkperm) { FILE *f; char line[1024]; int active, linenum; int bad_options = 0; if ((f = fopen(filename, "r")) == NULL) return 0; if (checkperm) { struct stat sb; if (fstat(fileno(f), &sb) == -1) fatal("fstat %s: %s", filename, strerror(errno)); if (((sb.st_uid != 0 && sb.st_uid != getuid()) || (sb.st_mode & 022) != 0)) fatal("Bad owner or permissions on %s", filename); } debug("Reading configuration data %.200s", filename); /* * Mark that we are now processing the options. This flag is turned * on/off by Host specifications. */ active = 1; linenum = 0; while (fgets(line, sizeof(line), f)) { /* Update line number counter. */ linenum++; if (process_config_line(options, host, line, filename, linenum, &active) != 0) bad_options++; } fclose(f); if (bad_options > 0) fatal("%s: terminating, %d bad configuration options", filename, bad_options); return 1; } /* * Initializes options to special values that indicate that they have not yet * been set. Read_config_file will only set options with this value. Options * are processed in the following order: command line, user config file, * system config file. Last, fill_default_options is called. */ void initialize_options(Options * options) { memset(options, 'X', sizeof(*options)); options->forward_agent = -1; options->forward_x11 = -1; options->forward_x11_trusted = -1; options->forward_x11_timeout = -1; options->exit_on_forward_failure = -1; options->xauth_location = NULL; options->gateway_ports = -1; options->use_privileged_port = -1; options->rsa_authentication = -1; options->pubkey_authentication = -1; options->challenge_response_authentication = -1; options->gss_authentication = -1; options->gss_deleg_creds = -1; options->password_authentication = -1; options->kbd_interactive_authentication = -1; options->kbd_interactive_devices = NULL; options->rhosts_rsa_authentication = -1; options->hostbased_authentication = -1; options->batch_mode = -1; options->check_host_ip = -1; options->strict_host_key_checking = -1; options->compression = -1; options->tcp_keep_alive = -1; options->compression_level = -1; options->port = -1; options->address_family = -1; options->connection_attempts = -1; options->connection_timeout = -1; options->number_of_password_prompts = -1; options->cipher = -1; options->ciphers = NULL; options->macs = NULL; options->kex_algorithms = NULL; options->hostkeyalgorithms = NULL; options->protocol = SSH_PROTO_UNKNOWN; options->num_identity_files = 0; options->hostname = NULL; options->host_key_alias = NULL; options->proxy_command = NULL; options->user = NULL; options->escape_char = -1; options->system_hostfile = NULL; options->user_hostfile = NULL; options->system_hostfile2 = NULL; options->user_hostfile2 = NULL; options->local_forwards = NULL; options->num_local_forwards = 0; options->remote_forwards = NULL; options->num_remote_forwards = 0; options->clear_forwardings = -1; options->log_level = SYSLOG_LEVEL_NOT_SET; options->preferred_authentications = NULL; options->bind_address = NULL; options->pkcs11_provider = NULL; options->enable_ssh_keysign = - 1; options->no_host_authentication_for_localhost = - 1; options->identities_only = - 1; options->rekey_limit = - 1; options->verify_host_key_dns = -1; options->server_alive_interval = -1; options->server_alive_count_max = -1; options->num_send_env = 0; options->control_path = NULL; options->control_master = -1; options->control_persist = -1; options->control_persist_timeout = 0; options->hash_known_hosts = -1; options->tun_open = -1; options->tun_local = -1; options->tun_remote = -1; options->local_command = NULL; options->permit_local_command = -1; options->use_roaming = -1; options->visual_host_key = -1; options->zero_knowledge_password_authentication = -1; options->ip_qos_interactive = -1; options->ip_qos_bulk = -1; + options->hpn_disabled = -1; + options->hpn_buffer_size = -1; + options->tcp_rcv_buf_poll = -1; + options->tcp_rcv_buf = -1; +#ifdef NONE_CIPHER_ENABLED + options->none_enabled = -1; + options->none_switch = -1; +#endif } /* * Called after processing other sources of option data, this fills those * options for which no value has been specified with their default values. */ void fill_default_options(Options * options) { int len; if (options->forward_agent == -1) options->forward_agent = 0; if (options->forward_x11 == -1) options->forward_x11 = 0; if (options->forward_x11_trusted == -1) options->forward_x11_trusted = 0; if (options->forward_x11_timeout == -1) options->forward_x11_timeout = 1200; if (options->exit_on_forward_failure == -1) options->exit_on_forward_failure = 0; if (options->xauth_location == NULL) options->xauth_location = _PATH_XAUTH; if (options->gateway_ports == -1) options->gateway_ports = 0; if (options->use_privileged_port == -1) options->use_privileged_port = 0; if (options->rsa_authentication == -1) options->rsa_authentication = 1; if (options->pubkey_authentication == -1) options->pubkey_authentication = 1; if (options->challenge_response_authentication == -1) options->challenge_response_authentication = 1; if (options->gss_authentication == -1) options->gss_authentication = 0; if (options->gss_deleg_creds == -1) options->gss_deleg_creds = 0; if (options->password_authentication == -1) options->password_authentication = 1; if (options->kbd_interactive_authentication == -1) options->kbd_interactive_authentication = 1; if (options->rhosts_rsa_authentication == -1) options->rhosts_rsa_authentication = 0; if (options->hostbased_authentication == -1) options->hostbased_authentication = 0; if (options->batch_mode == -1) options->batch_mode = 0; if (options->check_host_ip == -1) options->check_host_ip = 0; if (options->strict_host_key_checking == -1) options->strict_host_key_checking = 2; /* 2 is default */ if (options->compression == -1) options->compression = 0; if (options->tcp_keep_alive == -1) options->tcp_keep_alive = 1; if (options->compression_level == -1) options->compression_level = 6; if (options->port == -1) options->port = 0; /* Filled in ssh_connect. */ if (options->address_family == -1) options->address_family = AF_UNSPEC; if (options->connection_attempts == -1) options->connection_attempts = 1; if (options->number_of_password_prompts == -1) options->number_of_password_prompts = 3; /* Selected in ssh_login(). */ if (options->cipher == -1) options->cipher = SSH_CIPHER_NOT_SET; /* options->ciphers, default set in myproposals.h */ /* options->macs, default set in myproposals.h */ /* options->kex_algorithms, default set in myproposals.h */ /* options->hostkeyalgorithms, default set in myproposals.h */ if (options->protocol == SSH_PROTO_UNKNOWN) options->protocol = SSH_PROTO_2; if (options->num_identity_files == 0) { if (options->protocol & SSH_PROTO_1) { len = 2 + strlen(_PATH_SSH_CLIENT_IDENTITY) + 1; options->identity_files[options->num_identity_files] = xmalloc(len); snprintf(options->identity_files[options->num_identity_files++], len, "~/%.100s", _PATH_SSH_CLIENT_IDENTITY); } if (options->protocol & SSH_PROTO_2) { len = 2 + strlen(_PATH_SSH_CLIENT_ID_RSA) + 1; options->identity_files[options->num_identity_files] = xmalloc(len); snprintf(options->identity_files[options->num_identity_files++], len, "~/%.100s", _PATH_SSH_CLIENT_ID_RSA); len = 2 + strlen(_PATH_SSH_CLIENT_ID_DSA) + 1; options->identity_files[options->num_identity_files] = xmalloc(len); snprintf(options->identity_files[options->num_identity_files++], len, "~/%.100s", _PATH_SSH_CLIENT_ID_DSA); #ifdef OPENSSL_HAS_ECC len = 2 + strlen(_PATH_SSH_CLIENT_ID_ECDSA) + 1; options->identity_files[options->num_identity_files] = xmalloc(len); snprintf(options->identity_files[options->num_identity_files++], len, "~/%.100s", _PATH_SSH_CLIENT_ID_ECDSA); #endif } } if (options->escape_char == -1) options->escape_char = '~'; if (options->system_hostfile == NULL) options->system_hostfile = _PATH_SSH_SYSTEM_HOSTFILE; if (options->user_hostfile == NULL) options->user_hostfile = _PATH_SSH_USER_HOSTFILE; if (options->system_hostfile2 == NULL) options->system_hostfile2 = _PATH_SSH_SYSTEM_HOSTFILE2; if (options->user_hostfile2 == NULL) options->user_hostfile2 = _PATH_SSH_USER_HOSTFILE2; if (options->log_level == SYSLOG_LEVEL_NOT_SET) options->log_level = SYSLOG_LEVEL_INFO; if (options->clear_forwardings == 1) clear_forwardings(options); if (options->no_host_authentication_for_localhost == - 1) options->no_host_authentication_for_localhost = 0; if (options->identities_only == -1) options->identities_only = 0; if (options->enable_ssh_keysign == -1) options->enable_ssh_keysign = 0; if (options->rekey_limit == -1) options->rekey_limit = 0; if (options->verify_host_key_dns == -1) options->verify_host_key_dns = 0; if (options->server_alive_interval == -1) options->server_alive_interval = 0; if (options->server_alive_count_max == -1) options->server_alive_count_max = 3; if (options->control_master == -1) options->control_master = 0; if (options->control_persist == -1) { options->control_persist = 0; options->control_persist_timeout = 0; } if (options->hash_known_hosts == -1) options->hash_known_hosts = 0; if (options->tun_open == -1) options->tun_open = SSH_TUNMODE_NO; if (options->tun_local == -1) options->tun_local = SSH_TUNID_ANY; if (options->tun_remote == -1) options->tun_remote = SSH_TUNID_ANY; if (options->permit_local_command == -1) options->permit_local_command = 0; if (options->use_roaming == -1) options->use_roaming = 1; if (options->visual_host_key == -1) options->visual_host_key = 0; if (options->zero_knowledge_password_authentication == -1) options->zero_knowledge_password_authentication = 0; if (options->ip_qos_interactive == -1) options->ip_qos_interactive = IPTOS_LOWDELAY; if (options->ip_qos_bulk == -1) options->ip_qos_bulk = IPTOS_THROUGHPUT; /* options->local_command should not be set by default */ /* options->proxy_command should not be set by default */ /* options->user will be set in the main program if appropriate */ /* options->hostname will be set in the main program if appropriate */ /* options->host_key_alias should not be set by default */ /* options->preferred_authentications will be set in ssh */ + if (options->hpn_disabled == -1) + options->hpn_disabled = 0; + if (options->hpn_buffer_size > -1) + { + u_int maxlen; + + /* If a user tries to set the size to 0 set it to 1KB. */ + if (options->hpn_buffer_size == 0) + options->hpn_buffer_size = 1024; + /* Limit the buffer to BUFFER_MAX_LEN. */ + maxlen = buffer_get_max_len(); + if (options->hpn_buffer_size > (maxlen / 1024)) { + debug("User requested buffer larger than %ub: %ub. " + "Request reverted to %ub", maxlen, + options->hpn_buffer_size * 1024, maxlen); + options->hpn_buffer_size = maxlen; + } + debug("hpn_buffer_size set to %d", options->hpn_buffer_size); + } + if (options->tcp_rcv_buf == 0) + options->tcp_rcv_buf = 1; + if (options->tcp_rcv_buf > -1) + options->tcp_rcv_buf *= 1024; + if (options->tcp_rcv_buf_poll == -1) + options->tcp_rcv_buf_poll = 1; +#ifdef NONE_CIPHER_ENABLED + /* options->none_enabled must not be set by default */ + if (options->none_switch == -1) + options->none_switch = 0; +#endif } /* * parse_forward * parses a string containing a port forwarding specification of the form: * dynamicfwd == 0 * [listenhost:]listenport:connecthost:connectport * dynamicfwd == 1 * [listenhost:]listenport * returns number of arguments parsed or zero on error */ int parse_forward(Forward *fwd, const char *fwdspec, int dynamicfwd, int remotefwd) { int i; char *p, *cp, *fwdarg[4]; memset(fwd, '\0', sizeof(*fwd)); cp = p = xstrdup(fwdspec); /* skip leading spaces */ while (isspace(*cp)) cp++; for (i = 0; i < 4; ++i) if ((fwdarg[i] = hpdelim(&cp)) == NULL) break; /* Check for trailing garbage */ if (cp != NULL) i = 0; /* failure */ switch (i) { case 1: fwd->listen_host = NULL; fwd->listen_port = a2port(fwdarg[0]); fwd->connect_host = xstrdup("socks"); break; case 2: fwd->listen_host = xstrdup(cleanhostname(fwdarg[0])); fwd->listen_port = a2port(fwdarg[1]); fwd->connect_host = xstrdup("socks"); break; case 3: fwd->listen_host = NULL; fwd->listen_port = a2port(fwdarg[0]); fwd->connect_host = xstrdup(cleanhostname(fwdarg[1])); fwd->connect_port = a2port(fwdarg[2]); break; case 4: fwd->listen_host = xstrdup(cleanhostname(fwdarg[0])); fwd->listen_port = a2port(fwdarg[1]); fwd->connect_host = xstrdup(cleanhostname(fwdarg[2])); fwd->connect_port = a2port(fwdarg[3]); break; default: i = 0; /* failure */ } xfree(p); if (dynamicfwd) { if (!(i == 1 || i == 2)) goto fail_free; } else { if (!(i == 3 || i == 4)) goto fail_free; if (fwd->connect_port <= 0) goto fail_free; } if (fwd->listen_port < 0 || (!remotefwd && fwd->listen_port == 0)) goto fail_free; if (fwd->connect_host != NULL && strlen(fwd->connect_host) >= NI_MAXHOST) goto fail_free; if (fwd->listen_host != NULL && strlen(fwd->listen_host) >= NI_MAXHOST) goto fail_free; return (i); fail_free: if (fwd->connect_host != NULL) { xfree(fwd->connect_host); fwd->connect_host = NULL; } if (fwd->listen_host != NULL) { xfree(fwd->listen_host); fwd->listen_host = NULL; } return (0); } diff --git a/crypto/openssh/readconf.h b/crypto/openssh/readconf.h index ee160dfe7b1c..195f6e74d62a 100644 --- a/crypto/openssh/readconf.h +++ b/crypto/openssh/readconf.h @@ -1,154 +1,166 @@ /* $OpenBSD: readconf.h,v 1.88 2010/11/13 23:27:50 djm Exp $ */ +/* $FreeBSD$ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved * Functions for reading the configuration file. * * As far as I am concerned, the code I have written for this software * can be used freely for any purpose. Any derived versions of this * software must be clearly marked as such, and if the derived work is * incompatible with the protocol description in the RFC file, it must be * called by a name other than "ssh" or "Secure Shell". */ #ifndef READCONF_H #define READCONF_H /* Data structure for representing a forwarding request. */ typedef struct { char *listen_host; /* Host (address) to listen on. */ int listen_port; /* Port to forward. */ char *connect_host; /* Host to connect. */ int connect_port; /* Port to connect on connect_host. */ int allocated_port; /* Dynamically allocated listen port */ } Forward; /* Data structure for representing option data. */ #define MAX_SEND_ENV 256 typedef struct { int forward_agent; /* Forward authentication agent. */ int forward_x11; /* Forward X11 display. */ int forward_x11_timeout; /* Expiration for Cookies */ int forward_x11_trusted; /* Trust Forward X11 display. */ int exit_on_forward_failure; /* Exit if bind(2) fails for -L/-R */ char *xauth_location; /* Location for xauth program */ int gateway_ports; /* Allow remote connects to forwarded ports. */ int use_privileged_port; /* Don't use privileged port if false. */ int rhosts_rsa_authentication; /* Try rhosts with RSA * authentication. */ int rsa_authentication; /* Try RSA authentication. */ int pubkey_authentication; /* Try ssh2 pubkey authentication. */ int hostbased_authentication; /* ssh2's rhosts_rsa */ int challenge_response_authentication; /* Try S/Key or TIS, authentication. */ int gss_authentication; /* Try GSS authentication */ int gss_deleg_creds; /* Delegate GSS credentials */ int password_authentication; /* Try password * authentication. */ int kbd_interactive_authentication; /* Try keyboard-interactive auth. */ char *kbd_interactive_devices; /* Keyboard-interactive auth devices. */ int zero_knowledge_password_authentication; /* Try jpake */ int batch_mode; /* Batch mode: do not ask for passwords. */ int check_host_ip; /* Also keep track of keys for IP address */ int strict_host_key_checking; /* Strict host key checking. */ int compression; /* Compress packets in both directions. */ int compression_level; /* Compression level 1 (fast) to 9 * (best). */ int tcp_keep_alive; /* Set SO_KEEPALIVE. */ int ip_qos_interactive; /* IP ToS/DSCP/class for interactive */ int ip_qos_bulk; /* IP ToS/DSCP/class for bulk traffic */ LogLevel log_level; /* Level for logging. */ int port; /* Port to connect. */ int address_family; int connection_attempts; /* Max attempts (seconds) before * giving up */ int connection_timeout; /* Max time (seconds) before * aborting connection attempt */ int number_of_password_prompts; /* Max number of password * prompts. */ int cipher; /* Cipher to use. */ char *ciphers; /* SSH2 ciphers in order of preference. */ char *macs; /* SSH2 macs in order of preference. */ char *hostkeyalgorithms; /* SSH2 server key types in order of preference. */ char *kex_algorithms; /* SSH2 kex methods in order of preference. */ int protocol; /* Protocol in order of preference. */ char *hostname; /* Real host to connect. */ char *host_key_alias; /* hostname alias for .ssh/known_hosts */ char *proxy_command; /* Proxy command for connecting the host. */ char *user; /* User to log in as. */ int escape_char; /* Escape character; -2 = none */ char *system_hostfile;/* Path for /etc/ssh/ssh_known_hosts. */ char *user_hostfile; /* Path for $HOME/.ssh/known_hosts. */ char *system_hostfile2; char *user_hostfile2; char *preferred_authentications; char *bind_address; /* local socket address for connection to sshd */ char *pkcs11_provider; /* PKCS#11 provider */ int verify_host_key_dns; /* Verify host key using DNS */ int num_identity_files; /* Number of files for RSA/DSA identities. */ char *identity_files[SSH_MAX_IDENTITY_FILES]; Key *identity_keys[SSH_MAX_IDENTITY_FILES]; /* Local TCP/IP forward requests. */ int num_local_forwards; Forward *local_forwards; /* Remote TCP/IP forward requests. */ int num_remote_forwards; Forward *remote_forwards; int clear_forwardings; int enable_ssh_keysign; int64_t rekey_limit; int no_host_authentication_for_localhost; int identities_only; int server_alive_interval; int server_alive_count_max; int num_send_env; char *send_env[MAX_SEND_ENV]; char *control_path; int control_master; int control_persist; /* ControlPersist flag */ int control_persist_timeout; /* ControlPersist timeout (seconds) */ int hash_known_hosts; int tun_open; /* tun(4) */ int tun_local; /* force tun device (optional) */ int tun_remote; /* force tun device (optional) */ char *local_command; int permit_local_command; int visual_host_key; int use_roaming; + int hpn_disabled; /* Switch to disable HPN buffer management. */ + int hpn_buffer_size; /* User definable size for HPN buffer + * window. */ + int tcp_rcv_buf_poll; /* Option to poll recv buf every window + * transfer. */ + int tcp_rcv_buf; /* User switch to set tcp recv buffer. */ + +#ifdef NONE_CIPHER_ENABLED + int none_enabled; /* Allow none to be used */ + int none_switch; /* Use none cipher */ +#endif } Options; #define SSHCTL_MASTER_NO 0 #define SSHCTL_MASTER_YES 1 #define SSHCTL_MASTER_AUTO 2 #define SSHCTL_MASTER_ASK 3 #define SSHCTL_MASTER_AUTO_ASK 4 void initialize_options(Options *); void fill_default_options(Options *); int read_config_file(const char *, const char *, Options *, int); int parse_forward(Forward *, const char *, int, int); int process_config_line(Options *, const char *, char *, const char *, int, int *); void add_local_forward(Options *, const Forward *); void add_remote_forward(Options *, const Forward *); #endif /* READCONF_H */ diff --git a/crypto/openssh/servconf.c b/crypto/openssh/servconf.c index c742e13098cc..96761e7251de 100644 --- a/crypto/openssh/servconf.c +++ b/crypto/openssh/servconf.c @@ -1,1792 +1,1858 @@ /* $OpenBSD: servconf.c,v 1.213 2010/11/13 23:27:50 djm Exp $ */ +/* $FreeBSD$ */ /* * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved * * As far as I am concerned, the code I have written for this software * can be used freely for any purpose. Any derived versions of this * software must be clearly marked as such, and if the derived work is * incompatible with the protocol description in the RFC file, it must be * called by a name other than "ssh" or "Secure Shell". */ #include "includes.h" __RCSID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "openbsd-compat/sys-queue.h" #include "xmalloc.h" #include "ssh.h" #include "log.h" #include "buffer.h" #include "servconf.h" #include "compat.h" #include "pathnames.h" #include "misc.h" #include "cipher.h" #include "key.h" #include "kex.h" #include "mac.h" #include "match.h" #include "channels.h" #include "groupaccess.h" #include "version.h" static void add_listen_addr(ServerOptions *, char *, int); static void add_one_listen_addr(ServerOptions *, char *, int); /* Use of privilege separation or not */ extern int use_privsep; extern Buffer cfg; /* Initializes the server options to their default values. */ void initialize_server_options(ServerOptions *options) { memset(options, 0, sizeof(*options)); /* Portable-specific options */ options->use_pam = -1; /* Standard Options */ options->num_ports = 0; options->ports_from_cmdline = 0; options->listen_addrs = NULL; options->address_family = -1; options->num_host_key_files = 0; options->num_host_cert_files = 0; options->pid_file = NULL; options->server_key_bits = -1; options->login_grace_time = -1; options->key_regeneration_time = -1; options->permit_root_login = PERMIT_NOT_SET; options->ignore_rhosts = -1; options->ignore_user_known_hosts = -1; options->print_motd = -1; options->print_lastlog = -1; options->x11_forwarding = -1; options->x11_display_offset = -1; options->x11_use_localhost = -1; options->xauth_location = NULL; options->strict_modes = -1; options->tcp_keep_alive = -1; options->log_facility = SYSLOG_FACILITY_NOT_SET; options->log_level = SYSLOG_LEVEL_NOT_SET; options->rhosts_rsa_authentication = -1; options->hostbased_authentication = -1; options->hostbased_uses_name_from_packet_only = -1; options->rsa_authentication = -1; options->pubkey_authentication = -1; options->kerberos_authentication = -1; options->kerberos_or_local_passwd = -1; options->kerberos_ticket_cleanup = -1; options->kerberos_get_afs_token = -1; options->gss_authentication=-1; options->gss_cleanup_creds = -1; options->password_authentication = -1; options->kbd_interactive_authentication = -1; options->challenge_response_authentication = -1; options->permit_empty_passwd = -1; options->permit_user_env = -1; options->use_login = -1; options->compression = -1; options->allow_tcp_forwarding = -1; options->allow_agent_forwarding = -1; options->num_allow_users = 0; options->num_deny_users = 0; options->num_allow_groups = 0; options->num_deny_groups = 0; options->ciphers = NULL; options->macs = NULL; options->kex_algorithms = NULL; options->protocol = SSH_PROTO_UNKNOWN; options->gateway_ports = -1; options->num_subsystems = 0; options->max_startups_begin = -1; options->max_startups_rate = -1; options->max_startups = -1; options->max_authtries = -1; options->max_sessions = -1; options->banner = NULL; options->use_dns = -1; options->client_alive_interval = -1; options->client_alive_count_max = -1; options->authorized_keys_file = NULL; options->authorized_keys_file2 = NULL; options->num_accept_env = 0; options->permit_tun = -1; options->num_permitted_opens = -1; options->adm_forced_command = NULL; options->chroot_directory = NULL; options->zero_knowledge_password_authentication = -1; options->revoked_keys_file = NULL; options->trusted_user_ca_keys = NULL; options->authorized_principals_file = NULL; options->ip_qos_interactive = -1; options->ip_qos_bulk = -1; + options->hpn_disabled = -1; + options->hpn_buffer_size = -1; + options->tcp_rcv_buf_poll = -1; +#ifdef NONE_CIPHER_ENABLED + options->none_enabled = -1; +#endif } void fill_default_server_options(ServerOptions *options) { /* Portable-specific options */ if (options->use_pam == -1) options->use_pam = 1; /* Standard Options */ if (options->protocol == SSH_PROTO_UNKNOWN) options->protocol = SSH_PROTO_2; if (options->num_host_key_files == 0) { /* fill default hostkeys for protocols */ if (options->protocol & SSH_PROTO_1) options->host_key_files[options->num_host_key_files++] = _PATH_HOST_KEY_FILE; if (options->protocol & SSH_PROTO_2) { options->host_key_files[options->num_host_key_files++] = _PATH_HOST_RSA_KEY_FILE; options->host_key_files[options->num_host_key_files++] = _PATH_HOST_DSA_KEY_FILE; #ifdef OPENSSL_HAS_ECC options->host_key_files[options->num_host_key_files++] = _PATH_HOST_ECDSA_KEY_FILE; #endif } } /* No certificates by default */ if (options->num_ports == 0) options->ports[options->num_ports++] = SSH_DEFAULT_PORT; if (options->listen_addrs == NULL) add_listen_addr(options, NULL, 0); if (options->pid_file == NULL) options->pid_file = _PATH_SSH_DAEMON_PID_FILE; if (options->server_key_bits == -1) options->server_key_bits = 1024; if (options->login_grace_time == -1) options->login_grace_time = 120; if (options->key_regeneration_time == -1) options->key_regeneration_time = 3600; if (options->permit_root_login == PERMIT_NOT_SET) options->permit_root_login = PERMIT_NO; if (options->ignore_rhosts == -1) options->ignore_rhosts = 1; if (options->ignore_user_known_hosts == -1) options->ignore_user_known_hosts = 0; if (options->print_motd == -1) options->print_motd = 1; if (options->print_lastlog == -1) options->print_lastlog = 1; if (options->x11_forwarding == -1) options->x11_forwarding = 1; if (options->x11_display_offset == -1) options->x11_display_offset = 10; if (options->x11_use_localhost == -1) options->x11_use_localhost = 1; if (options->xauth_location == NULL) options->xauth_location = _PATH_XAUTH; if (options->strict_modes == -1) options->strict_modes = 1; if (options->tcp_keep_alive == -1) options->tcp_keep_alive = 1; if (options->log_facility == SYSLOG_FACILITY_NOT_SET) options->log_facility = SYSLOG_FACILITY_AUTH; if (options->log_level == SYSLOG_LEVEL_NOT_SET) options->log_level = SYSLOG_LEVEL_INFO; if (options->rhosts_rsa_authentication == -1) options->rhosts_rsa_authentication = 0; if (options->hostbased_authentication == -1) options->hostbased_authentication = 0; if (options->hostbased_uses_name_from_packet_only == -1) options->hostbased_uses_name_from_packet_only = 0; if (options->rsa_authentication == -1) options->rsa_authentication = 1; if (options->pubkey_authentication == -1) options->pubkey_authentication = 1; if (options->kerberos_authentication == -1) options->kerberos_authentication = 0; if (options->kerberos_or_local_passwd == -1) options->kerberos_or_local_passwd = 1; if (options->kerberos_ticket_cleanup == -1) options->kerberos_ticket_cleanup = 1; if (options->kerberos_get_afs_token == -1) options->kerberos_get_afs_token = 0; if (options->gss_authentication == -1) options->gss_authentication = 0; if (options->gss_cleanup_creds == -1) options->gss_cleanup_creds = 1; if (options->password_authentication == -1) options->password_authentication = 0; if (options->kbd_interactive_authentication == -1) options->kbd_interactive_authentication = 0; if (options->challenge_response_authentication == -1) options->challenge_response_authentication = 1; if (options->permit_empty_passwd == -1) options->permit_empty_passwd = 0; if (options->permit_user_env == -1) options->permit_user_env = 0; if (options->use_login == -1) options->use_login = 0; if (options->compression == -1) options->compression = COMP_DELAYED; if (options->allow_tcp_forwarding == -1) options->allow_tcp_forwarding = 1; if (options->allow_agent_forwarding == -1) options->allow_agent_forwarding = 1; if (options->gateway_ports == -1) options->gateway_ports = 0; if (options->max_startups == -1) options->max_startups = 10; if (options->max_startups_rate == -1) options->max_startups_rate = 100; /* 100% */ if (options->max_startups_begin == -1) options->max_startups_begin = options->max_startups; if (options->max_authtries == -1) options->max_authtries = DEFAULT_AUTH_FAIL_MAX; if (options->max_sessions == -1) options->max_sessions = DEFAULT_SESSIONS_MAX; if (options->use_dns == -1) options->use_dns = 1; if (options->client_alive_interval == -1) options->client_alive_interval = 0; if (options->client_alive_count_max == -1) options->client_alive_count_max = 3; if (options->authorized_keys_file2 == NULL) { /* authorized_keys_file2 falls back to authorized_keys_file */ if (options->authorized_keys_file != NULL) options->authorized_keys_file2 = xstrdup(options->authorized_keys_file); else options->authorized_keys_file2 = xstrdup(_PATH_SSH_USER_PERMITTED_KEYS2); } if (options->authorized_keys_file == NULL) options->authorized_keys_file = xstrdup(_PATH_SSH_USER_PERMITTED_KEYS); if (options->permit_tun == -1) options->permit_tun = SSH_TUNMODE_NO; if (options->zero_knowledge_password_authentication == -1) options->zero_knowledge_password_authentication = 0; if (options->ip_qos_interactive == -1) options->ip_qos_interactive = IPTOS_LOWDELAY; if (options->ip_qos_bulk == -1) options->ip_qos_bulk = IPTOS_THROUGHPUT; + if (options->hpn_disabled == -1) + options->hpn_disabled = 0; + if (options->hpn_buffer_size == -1) { + /* + * HPN buffer size option not explicitly set. Try to figure + * out what value to use or resort to default. + */ + options->hpn_buffer_size = CHAN_SES_WINDOW_DEFAULT; + if (!options->hpn_disabled) { + sock_get_rcvbuf(&options->hpn_buffer_size, 0); + debug ("HPN Buffer Size: %d", options->hpn_buffer_size); + } + } else { + /* + * In the case that the user sets both values in a + * contradictory manner hpn_disabled overrrides hpn_buffer_size. + */ + if (options->hpn_disabled <= 0) { + u_int maxlen; + + maxlen = buffer_get_max_len(); + if (options->hpn_buffer_size == 0) + options->hpn_buffer_size = 1; + /* Limit the maximum buffer to BUFFER_MAX_LEN. */ + if (options->hpn_buffer_size > maxlen / 1024) + options->hpn_buffer_size = maxlen; + else + options->hpn_buffer_size *= 1024; + } else + options->hpn_buffer_size = CHAN_TCP_WINDOW_DEFAULT; + } /* Turn privilege separation on by default */ if (use_privsep == -1) use_privsep = 1; #ifndef HAVE_MMAP if (use_privsep && options->compression == 1) { error("This platform does not support both privilege " "separation and compression"); error("Compression disabled"); options->compression = 0; } #endif } /* Keyword tokens. */ typedef enum { sBadOption, /* == unknown option */ /* Portable-specific options */ sUsePAM, /* Standard Options */ sPort, sHostKeyFile, sServerKeyBits, sLoginGraceTime, sKeyRegenerationTime, sPermitRootLogin, sLogFacility, sLogLevel, sRhostsRSAAuthentication, sRSAAuthentication, sKerberosAuthentication, sKerberosOrLocalPasswd, sKerberosTicketCleanup, sKerberosGetAFSToken, sKerberosTgtPassing, sChallengeResponseAuthentication, sPasswordAuthentication, sKbdInteractiveAuthentication, sListenAddress, sAddressFamily, sPrintMotd, sPrintLastLog, sIgnoreRhosts, sX11Forwarding, sX11DisplayOffset, sX11UseLocalhost, sStrictModes, sEmptyPasswd, sTCPKeepAlive, sPermitUserEnvironment, sUseLogin, sAllowTcpForwarding, sCompression, sAllowUsers, sDenyUsers, sAllowGroups, sDenyGroups, sIgnoreUserKnownHosts, sCiphers, sMacs, sProtocol, sPidFile, sGatewayPorts, sPubkeyAuthentication, sXAuthLocation, sSubsystem, sMaxStartups, sMaxAuthTries, sMaxSessions, sBanner, sUseDNS, sHostbasedAuthentication, sHostbasedUsesNameFromPacketOnly, sClientAliveInterval, sClientAliveCountMax, sAuthorizedKeysFile, sAuthorizedKeysFile2, sGssAuthentication, sGssCleanupCreds, sAcceptEnv, sPermitTunnel, sMatch, sPermitOpen, sForceCommand, sChrootDirectory, sUsePrivilegeSeparation, sAllowAgentForwarding, sZeroKnowledgePasswordAuthentication, sHostCertificate, sRevokedKeys, sTrustedUserCAKeys, sAuthorizedPrincipalsFile, sKexAlgorithms, sIPQoS, + sHPNDisabled, sHPNBufferSize, sTcpRcvBufPoll, +#ifdef NONE_CIPHER_ENABLED + sNoneEnabled, +#endif sVersionAddendum, sDeprecated, sUnsupported } ServerOpCodes; #define SSHCFG_GLOBAL 0x01 /* allowed in main section of sshd_config */ #define SSHCFG_MATCH 0x02 /* allowed inside a Match section */ #define SSHCFG_ALL (SSHCFG_GLOBAL|SSHCFG_MATCH) /* Textual representation of the tokens. */ static struct { const char *name; ServerOpCodes opcode; u_int flags; } keywords[] = { /* Portable-specific options */ #ifdef USE_PAM { "usepam", sUsePAM, SSHCFG_GLOBAL }, #else { "usepam", sUnsupported, SSHCFG_GLOBAL }, #endif { "pamauthenticationviakbdint", sDeprecated, SSHCFG_GLOBAL }, /* Standard Options */ { "port", sPort, SSHCFG_GLOBAL }, { "hostkey", sHostKeyFile, SSHCFG_GLOBAL }, { "hostdsakey", sHostKeyFile, SSHCFG_GLOBAL }, /* alias */ { "pidfile", sPidFile, SSHCFG_GLOBAL }, { "serverkeybits", sServerKeyBits, SSHCFG_GLOBAL }, { "logingracetime", sLoginGraceTime, SSHCFG_GLOBAL }, { "keyregenerationinterval", sKeyRegenerationTime, SSHCFG_GLOBAL }, { "permitrootlogin", sPermitRootLogin, SSHCFG_ALL }, { "syslogfacility", sLogFacility, SSHCFG_GLOBAL }, { "loglevel", sLogLevel, SSHCFG_GLOBAL }, { "rhostsauthentication", sDeprecated, SSHCFG_GLOBAL }, { "rhostsrsaauthentication", sRhostsRSAAuthentication, SSHCFG_ALL }, { "hostbasedauthentication", sHostbasedAuthentication, SSHCFG_ALL }, { "hostbasedusesnamefrompacketonly", sHostbasedUsesNameFromPacketOnly, SSHCFG_ALL }, { "rsaauthentication", sRSAAuthentication, SSHCFG_ALL }, { "pubkeyauthentication", sPubkeyAuthentication, SSHCFG_ALL }, { "dsaauthentication", sPubkeyAuthentication, SSHCFG_GLOBAL }, /* alias */ #ifdef KRB5 { "kerberosauthentication", sKerberosAuthentication, SSHCFG_ALL }, { "kerberosorlocalpasswd", sKerberosOrLocalPasswd, SSHCFG_GLOBAL }, { "kerberosticketcleanup", sKerberosTicketCleanup, SSHCFG_GLOBAL }, #ifdef USE_AFS { "kerberosgetafstoken", sKerberosGetAFSToken, SSHCFG_GLOBAL }, #else { "kerberosgetafstoken", sUnsupported, SSHCFG_GLOBAL }, #endif #else { "kerberosauthentication", sUnsupported, SSHCFG_ALL }, { "kerberosorlocalpasswd", sUnsupported, SSHCFG_GLOBAL }, { "kerberosticketcleanup", sUnsupported, SSHCFG_GLOBAL }, { "kerberosgetafstoken", sUnsupported, SSHCFG_GLOBAL }, #endif { "kerberostgtpassing", sUnsupported, SSHCFG_GLOBAL }, { "afstokenpassing", sUnsupported, SSHCFG_GLOBAL }, #ifdef GSSAPI { "gssapiauthentication", sGssAuthentication, SSHCFG_ALL }, { "gssapicleanupcredentials", sGssCleanupCreds, SSHCFG_GLOBAL }, #else { "gssapiauthentication", sUnsupported, SSHCFG_ALL }, { "gssapicleanupcredentials", sUnsupported, SSHCFG_GLOBAL }, #endif { "passwordauthentication", sPasswordAuthentication, SSHCFG_ALL }, { "kbdinteractiveauthentication", sKbdInteractiveAuthentication, SSHCFG_ALL }, { "challengeresponseauthentication", sChallengeResponseAuthentication, SSHCFG_GLOBAL }, { "skeyauthentication", sChallengeResponseAuthentication, SSHCFG_GLOBAL }, /* alias */ #ifdef JPAKE { "zeroknowledgepasswordauthentication", sZeroKnowledgePasswordAuthentication, SSHCFG_ALL }, #else { "zeroknowledgepasswordauthentication", sUnsupported, SSHCFG_ALL }, #endif { "checkmail", sDeprecated, SSHCFG_GLOBAL }, { "listenaddress", sListenAddress, SSHCFG_GLOBAL }, { "addressfamily", sAddressFamily, SSHCFG_GLOBAL }, { "printmotd", sPrintMotd, SSHCFG_GLOBAL }, { "printlastlog", sPrintLastLog, SSHCFG_GLOBAL }, { "ignorerhosts", sIgnoreRhosts, SSHCFG_GLOBAL }, { "ignoreuserknownhosts", sIgnoreUserKnownHosts, SSHCFG_GLOBAL }, { "x11forwarding", sX11Forwarding, SSHCFG_ALL }, { "x11displayoffset", sX11DisplayOffset, SSHCFG_ALL }, { "x11uselocalhost", sX11UseLocalhost, SSHCFG_ALL }, { "xauthlocation", sXAuthLocation, SSHCFG_GLOBAL }, { "strictmodes", sStrictModes, SSHCFG_GLOBAL }, { "permitemptypasswords", sEmptyPasswd, SSHCFG_ALL }, { "permituserenvironment", sPermitUserEnvironment, SSHCFG_GLOBAL }, { "uselogin", sUseLogin, SSHCFG_GLOBAL }, { "compression", sCompression, SSHCFG_GLOBAL }, { "tcpkeepalive", sTCPKeepAlive, SSHCFG_GLOBAL }, { "keepalive", sTCPKeepAlive, SSHCFG_GLOBAL }, /* obsolete alias */ { "allowtcpforwarding", sAllowTcpForwarding, SSHCFG_ALL }, { "allowagentforwarding", sAllowAgentForwarding, SSHCFG_ALL }, { "allowusers", sAllowUsers, SSHCFG_GLOBAL }, { "denyusers", sDenyUsers, SSHCFG_GLOBAL }, { "allowgroups", sAllowGroups, SSHCFG_GLOBAL }, { "denygroups", sDenyGroups, SSHCFG_GLOBAL }, { "ciphers", sCiphers, SSHCFG_GLOBAL }, { "macs", sMacs, SSHCFG_GLOBAL }, { "protocol", sProtocol, SSHCFG_GLOBAL }, { "gatewayports", sGatewayPorts, SSHCFG_ALL }, { "subsystem", sSubsystem, SSHCFG_GLOBAL }, { "maxstartups", sMaxStartups, SSHCFG_GLOBAL }, { "maxauthtries", sMaxAuthTries, SSHCFG_ALL }, { "maxsessions", sMaxSessions, SSHCFG_ALL }, { "banner", sBanner, SSHCFG_ALL }, { "usedns", sUseDNS, SSHCFG_GLOBAL }, { "verifyreversemapping", sDeprecated, SSHCFG_GLOBAL }, { "reversemappingcheck", sDeprecated, SSHCFG_GLOBAL }, { "clientaliveinterval", sClientAliveInterval, SSHCFG_GLOBAL }, { "clientalivecountmax", sClientAliveCountMax, SSHCFG_GLOBAL }, { "authorizedkeysfile", sAuthorizedKeysFile, SSHCFG_ALL }, { "authorizedkeysfile2", sAuthorizedKeysFile2, SSHCFG_ALL }, { "useprivilegeseparation", sUsePrivilegeSeparation, SSHCFG_GLOBAL}, { "acceptenv", sAcceptEnv, SSHCFG_GLOBAL }, { "permittunnel", sPermitTunnel, SSHCFG_ALL }, { "match", sMatch, SSHCFG_ALL }, { "permitopen", sPermitOpen, SSHCFG_ALL }, { "forcecommand", sForceCommand, SSHCFG_ALL }, { "chrootdirectory", sChrootDirectory, SSHCFG_ALL }, { "hostcertificate", sHostCertificate, SSHCFG_GLOBAL }, { "revokedkeys", sRevokedKeys, SSHCFG_ALL }, { "trustedusercakeys", sTrustedUserCAKeys, SSHCFG_ALL }, { "authorizedprincipalsfile", sAuthorizedPrincipalsFile, SSHCFG_ALL }, { "kexalgorithms", sKexAlgorithms, SSHCFG_GLOBAL }, { "ipqos", sIPQoS, SSHCFG_ALL }, + { "hpndisabled", sHPNDisabled, SSHCFG_ALL }, + { "hpnbuffersize", sHPNBufferSize, SSHCFG_ALL }, + { "tcprcvbufpoll", sTcpRcvBufPoll, SSHCFG_ALL }, +#ifdef NONE_CIPHER_ENABLED + { "noneenabled", sNoneEnabled, SSHCFG_ALL }, +#endif { "versionaddendum", sVersionAddendum, SSHCFG_GLOBAL }, { NULL, sBadOption, 0 } }; static struct { int val; char *text; } tunmode_desc[] = { { SSH_TUNMODE_NO, "no" }, { SSH_TUNMODE_POINTOPOINT, "point-to-point" }, { SSH_TUNMODE_ETHERNET, "ethernet" }, { SSH_TUNMODE_YES, "yes" }, { -1, NULL } }; /* * Returns the number of the token pointed to by cp or sBadOption. */ static ServerOpCodes parse_token(const char *cp, const char *filename, int linenum, u_int *flags) { u_int i; for (i = 0; keywords[i].name; i++) if (strcasecmp(cp, keywords[i].name) == 0) { *flags = keywords[i].flags; return keywords[i].opcode; } error("%s: line %d: Bad configuration option: %s", filename, linenum, cp); return sBadOption; } char * derelativise_path(const char *path) { char *expanded, *ret, cwd[MAXPATHLEN]; expanded = tilde_expand_filename(path, getuid()); if (*expanded == '/') return expanded; if (getcwd(cwd, sizeof(cwd)) == NULL) fatal("%s: getcwd: %s", __func__, strerror(errno)); xasprintf(&ret, "%s/%s", cwd, expanded); xfree(expanded); return ret; } static void add_listen_addr(ServerOptions *options, char *addr, int port) { u_int i; if (options->num_ports == 0) options->ports[options->num_ports++] = SSH_DEFAULT_PORT; if (options->address_family == -1) options->address_family = AF_UNSPEC; if (port == 0) for (i = 0; i < options->num_ports; i++) add_one_listen_addr(options, addr, options->ports[i]); else add_one_listen_addr(options, addr, port); } static void add_one_listen_addr(ServerOptions *options, char *addr, int port) { struct addrinfo hints, *ai, *aitop; char strport[NI_MAXSERV]; int gaierr; memset(&hints, 0, sizeof(hints)); hints.ai_family = options->address_family; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = (addr == NULL) ? AI_PASSIVE : 0; snprintf(strport, sizeof strport, "%d", port); if ((gaierr = getaddrinfo(addr, strport, &hints, &aitop)) != 0) fatal("bad addr or host: %s (%s)", addr ? addr : "", ssh_gai_strerror(gaierr)); for (ai = aitop; ai->ai_next; ai = ai->ai_next) ; ai->ai_next = options->listen_addrs; options->listen_addrs = aitop; } /* * The strategy for the Match blocks is that the config file is parsed twice. * * The first time is at startup. activep is initialized to 1 and the * directives in the global context are processed and acted on. Hitting a * Match directive unsets activep and the directives inside the block are * checked for syntax only. * * The second time is after a connection has been established but before * authentication. activep is initialized to 2 and global config directives * are ignored since they have already been processed. If the criteria in a * Match block is met, activep is set and the subsequent directives * processed and actioned until EOF or another Match block unsets it. Any * options set are copied into the main server config. * * Potential additions/improvements: * - Add Match support for pre-kex directives, eg Protocol, Ciphers. * * - Add a Tag directive (idea from David Leonard) ala pf, eg: * Match Address 192.168.0.* * Tag trusted * Match Group wheel * Tag trusted * Match Tag trusted * AllowTcpForwarding yes * GatewayPorts clientspecified * [...] * * - Add a PermittedChannelRequests directive * Match Group shell * PermittedChannelRequests session,forwarded-tcpip */ static int match_cfg_line_group(const char *grps, int line, const char *user) { int result = 0; struct passwd *pw; if (user == NULL) goto out; if ((pw = getpwnam(user)) == NULL) { debug("Can't match group at line %d because user %.100s does " "not exist", line, user); } else if (ga_init(pw->pw_name, pw->pw_gid) == 0) { debug("Can't Match group because user %.100s not in any group " "at line %d", user, line); } else if (ga_match_pattern_list(grps) != 1) { debug("user %.100s does not match group list %.100s at line %d", user, grps, line); } else { debug("user %.100s matched group list %.100s at line %d", user, grps, line); result = 1; } out: ga_free(); return result; } static int match_cfg_line(char **condition, int line, const char *user, const char *host, const char *address) { int result = 1; char *arg, *attrib, *cp = *condition; size_t len; if (user == NULL) debug3("checking syntax for 'Match %s'", cp); else debug3("checking match for '%s' user %s host %s addr %s", cp, user ? user : "(null)", host ? host : "(null)", address ? address : "(null)"); while ((attrib = strdelim(&cp)) && *attrib != '\0') { if ((arg = strdelim(&cp)) == NULL || *arg == '\0') { error("Missing Match criteria for %s", attrib); return -1; } len = strlen(arg); if (strcasecmp(attrib, "user") == 0) { if (!user) { result = 0; continue; } if (match_pattern_list(user, arg, len, 0) != 1) result = 0; else debug("user %.100s matched 'User %.100s' at " "line %d", user, arg, line); } else if (strcasecmp(attrib, "group") == 0) { switch (match_cfg_line_group(arg, line, user)) { case -1: return -1; case 0: result = 0; } } else if (strcasecmp(attrib, "host") == 0) { if (!host) { result = 0; continue; } if (match_hostname(host, arg, len) != 1) result = 0; else debug("connection from %.100s matched 'Host " "%.100s' at line %d", host, arg, line); } else if (strcasecmp(attrib, "address") == 0) { switch (addr_match_list(address, arg)) { case 1: debug("connection from %.100s matched 'Address " "%.100s' at line %d", address, arg, line); break; case 0: case -1: result = 0; break; case -2: return -1; } } else { error("Unsupported Match attribute %s", attrib); return -1; } } if (user != NULL) debug3("match %sfound", result ? "" : "not "); *condition = cp; return result; } #define WHITESPACE " \t\r\n" int process_server_config_line(ServerOptions *options, char *line, const char *filename, int linenum, int *activep, const char *user, const char *host, const char *address) { char *cp, **charptr, *arg, *p; int cmdline = 0, *intptr, value, value2, n; SyslogFacility *log_facility_ptr; LogLevel *log_level_ptr; ServerOpCodes opcode; int port; u_int i, flags = 0; size_t len; cp = line; if ((arg = strdelim(&cp)) == NULL) return 0; /* Ignore leading whitespace */ if (*arg == '\0') arg = strdelim(&cp); if (!arg || !*arg || *arg == '#') return 0; intptr = NULL; charptr = NULL; opcode = parse_token(arg, filename, linenum, &flags); if (activep == NULL) { /* We are processing a command line directive */ cmdline = 1; activep = &cmdline; } if (*activep && opcode != sMatch) debug3("%s:%d setting %s %s", filename, linenum, arg, cp); if (*activep == 0 && !(flags & SSHCFG_MATCH)) { if (user == NULL) { fatal("%s line %d: Directive '%s' is not allowed " "within a Match block", filename, linenum, arg); } else { /* this is a directive we have already processed */ while (arg) arg = strdelim(&cp); return 0; } } switch (opcode) { /* Portable-specific options */ case sUsePAM: intptr = &options->use_pam; goto parse_flag; /* Standard Options */ case sBadOption: return -1; case sPort: /* ignore ports from configfile if cmdline specifies ports */ if (options->ports_from_cmdline) return 0; if (options->listen_addrs != NULL) fatal("%s line %d: ports must be specified before " "ListenAddress.", filename, linenum); if (options->num_ports >= MAX_PORTS) fatal("%s line %d: too many ports.", filename, linenum); arg = strdelim(&cp); if (!arg || *arg == '\0') fatal("%s line %d: missing port number.", filename, linenum); options->ports[options->num_ports++] = a2port(arg); if (options->ports[options->num_ports-1] <= 0) fatal("%s line %d: Badly formatted port number.", filename, linenum); break; case sServerKeyBits: intptr = &options->server_key_bits; parse_int: arg = strdelim(&cp); if (!arg || *arg == '\0') fatal("%s line %d: missing integer value.", filename, linenum); value = atoi(arg); if (*activep && *intptr == -1) *intptr = value; break; case sLoginGraceTime: intptr = &options->login_grace_time; parse_time: arg = strdelim(&cp); if (!arg || *arg == '\0') fatal("%s line %d: missing time value.", filename, linenum); if ((value = convtime(arg)) == -1) fatal("%s line %d: invalid time value.", filename, linenum); if (*intptr == -1) *intptr = value; break; case sKeyRegenerationTime: intptr = &options->key_regeneration_time; goto parse_time; case sListenAddress: arg = strdelim(&cp); if (arg == NULL || *arg == '\0') fatal("%s line %d: missing address", filename, linenum); /* check for bare IPv6 address: no "[]" and 2 or more ":" */ if (strchr(arg, '[') == NULL && (p = strchr(arg, ':')) != NULL && strchr(p+1, ':') != NULL) { add_listen_addr(options, arg, 0); break; } p = hpdelim(&arg); if (p == NULL) fatal("%s line %d: bad address:port usage", filename, linenum); p = cleanhostname(p); if (arg == NULL) port = 0; else if ((port = a2port(arg)) <= 0) fatal("%s line %d: bad port number", filename, linenum); add_listen_addr(options, p, port); break; case sAddressFamily: arg = strdelim(&cp); if (!arg || *arg == '\0') fatal("%s line %d: missing address family.", filename, linenum); intptr = &options->address_family; if (options->listen_addrs != NULL) fatal("%s line %d: address family must be specified before " "ListenAddress.", filename, linenum); if (strcasecmp(arg, "inet") == 0) value = AF_INET; else if (strcasecmp(arg, "inet6") == 0) value = AF_INET6; else if (strcasecmp(arg, "any") == 0) value = AF_UNSPEC; else fatal("%s line %d: unsupported address family \"%s\".", filename, linenum, arg); if (*intptr == -1) *intptr = value; break; case sHostKeyFile: intptr = &options->num_host_key_files; if (*intptr >= MAX_HOSTKEYS) fatal("%s line %d: too many host keys specified (max %d).", filename, linenum, MAX_HOSTKEYS); charptr = &options->host_key_files[*intptr]; parse_filename: arg = strdelim(&cp); if (!arg || *arg == '\0') fatal("%s line %d: missing file name.", filename, linenum); if (*activep && *charptr == NULL) { *charptr = derelativise_path(arg); /* increase optional counter */ if (intptr != NULL) *intptr = *intptr + 1; } break; case sHostCertificate: intptr = &options->num_host_cert_files; if (*intptr >= MAX_HOSTKEYS) fatal("%s line %d: too many host certificates " "specified (max %d).", filename, linenum, MAX_HOSTCERTS); charptr = &options->host_cert_files[*intptr]; goto parse_filename; break; case sPidFile: charptr = &options->pid_file; goto parse_filename; case sPermitRootLogin: intptr = &options->permit_root_login; arg = strdelim(&cp); if (!arg || *arg == '\0') fatal("%s line %d: missing yes/" "without-password/forced-commands-only/no " "argument.", filename, linenum); value = 0; /* silence compiler */ if (strcmp(arg, "without-password") == 0) value = PERMIT_NO_PASSWD; else if (strcmp(arg, "forced-commands-only") == 0) value = PERMIT_FORCED_ONLY; else if (strcmp(arg, "yes") == 0) value = PERMIT_YES; else if (strcmp(arg, "no") == 0) value = PERMIT_NO; else fatal("%s line %d: Bad yes/" "without-password/forced-commands-only/no " "argument: %s", filename, linenum, arg); if (*activep && *intptr == -1) *intptr = value; break; case sIgnoreRhosts: intptr = &options->ignore_rhosts; parse_flag: arg = strdelim(&cp); if (!arg || *arg == '\0') fatal("%s line %d: missing yes/no argument.", filename, linenum); value = 0; /* silence compiler */ if (strcmp(arg, "yes") == 0) value = 1; else if (strcmp(arg, "no") == 0) value = 0; else fatal("%s line %d: Bad yes/no argument: %s", filename, linenum, arg); if (*activep && *intptr == -1) *intptr = value; break; case sIgnoreUserKnownHosts: intptr = &options->ignore_user_known_hosts; goto parse_flag; case sRhostsRSAAuthentication: intptr = &options->rhosts_rsa_authentication; goto parse_flag; case sHostbasedAuthentication: intptr = &options->hostbased_authentication; goto parse_flag; case sHostbasedUsesNameFromPacketOnly: intptr = &options->hostbased_uses_name_from_packet_only; goto parse_flag; case sRSAAuthentication: intptr = &options->rsa_authentication; goto parse_flag; case sPubkeyAuthentication: intptr = &options->pubkey_authentication; goto parse_flag; case sKerberosAuthentication: intptr = &options->kerberos_authentication; goto parse_flag; case sKerberosOrLocalPasswd: intptr = &options->kerberos_or_local_passwd; goto parse_flag; case sKerberosTicketCleanup: intptr = &options->kerberos_ticket_cleanup; goto parse_flag; case sKerberosGetAFSToken: intptr = &options->kerberos_get_afs_token; goto parse_flag; case sGssAuthentication: intptr = &options->gss_authentication; goto parse_flag; case sGssCleanupCreds: intptr = &options->gss_cleanup_creds; goto parse_flag; case sPasswordAuthentication: intptr = &options->password_authentication; goto parse_flag; case sZeroKnowledgePasswordAuthentication: intptr = &options->zero_knowledge_password_authentication; goto parse_flag; case sKbdInteractiveAuthentication: intptr = &options->kbd_interactive_authentication; goto parse_flag; case sChallengeResponseAuthentication: intptr = &options->challenge_response_authentication; goto parse_flag; case sPrintMotd: intptr = &options->print_motd; goto parse_flag; case sPrintLastLog: intptr = &options->print_lastlog; goto parse_flag; case sX11Forwarding: intptr = &options->x11_forwarding; goto parse_flag; case sX11DisplayOffset: intptr = &options->x11_display_offset; goto parse_int; case sX11UseLocalhost: intptr = &options->x11_use_localhost; goto parse_flag; case sXAuthLocation: charptr = &options->xauth_location; goto parse_filename; case sStrictModes: intptr = &options->strict_modes; goto parse_flag; case sTCPKeepAlive: intptr = &options->tcp_keep_alive; goto parse_flag; case sEmptyPasswd: intptr = &options->permit_empty_passwd; goto parse_flag; case sPermitUserEnvironment: intptr = &options->permit_user_env; goto parse_flag; case sUseLogin: intptr = &options->use_login; goto parse_flag; case sCompression: intptr = &options->compression; arg = strdelim(&cp); if (!arg || *arg == '\0') fatal("%s line %d: missing yes/no/delayed " "argument.", filename, linenum); value = 0; /* silence compiler */ if (strcmp(arg, "delayed") == 0) value = COMP_DELAYED; else if (strcmp(arg, "yes") == 0) value = COMP_ZLIB; else if (strcmp(arg, "no") == 0) value = COMP_NONE; else fatal("%s line %d: Bad yes/no/delayed " "argument: %s", filename, linenum, arg); if (*intptr == -1) *intptr = value; break; case sGatewayPorts: intptr = &options->gateway_ports; arg = strdelim(&cp); if (!arg || *arg == '\0') fatal("%s line %d: missing yes/no/clientspecified " "argument.", filename, linenum); value = 0; /* silence compiler */ if (strcmp(arg, "clientspecified") == 0) value = 2; else if (strcmp(arg, "yes") == 0) value = 1; else if (strcmp(arg, "no") == 0) value = 0; else fatal("%s line %d: Bad yes/no/clientspecified " "argument: %s", filename, linenum, arg); if (*activep && *intptr == -1) *intptr = value; break; case sUseDNS: intptr = &options->use_dns; goto parse_flag; case sLogFacility: log_facility_ptr = &options->log_facility; arg = strdelim(&cp); value = log_facility_number(arg); if (value == SYSLOG_FACILITY_NOT_SET) fatal("%.200s line %d: unsupported log facility '%s'", filename, linenum, arg ? arg : ""); if (*log_facility_ptr == -1) *log_facility_ptr = (SyslogFacility) value; break; case sLogLevel: log_level_ptr = &options->log_level; arg = strdelim(&cp); value = log_level_number(arg); if (value == SYSLOG_LEVEL_NOT_SET) fatal("%.200s line %d: unsupported log level '%s'", filename, linenum, arg ? arg : ""); if (*log_level_ptr == -1) *log_level_ptr = (LogLevel) value; break; case sAllowTcpForwarding: intptr = &options->allow_tcp_forwarding; goto parse_flag; case sAllowAgentForwarding: intptr = &options->allow_agent_forwarding; goto parse_flag; case sUsePrivilegeSeparation: intptr = &use_privsep; goto parse_flag; case sAllowUsers: while ((arg = strdelim(&cp)) && *arg != '\0') { if (options->num_allow_users >= MAX_ALLOW_USERS) fatal("%s line %d: too many allow users.", filename, linenum); options->allow_users[options->num_allow_users++] = xstrdup(arg); } break; case sDenyUsers: while ((arg = strdelim(&cp)) && *arg != '\0') { if (options->num_deny_users >= MAX_DENY_USERS) fatal("%s line %d: too many deny users.", filename, linenum); options->deny_users[options->num_deny_users++] = xstrdup(arg); } break; case sAllowGroups: while ((arg = strdelim(&cp)) && *arg != '\0') { if (options->num_allow_groups >= MAX_ALLOW_GROUPS) fatal("%s line %d: too many allow groups.", filename, linenum); options->allow_groups[options->num_allow_groups++] = xstrdup(arg); } break; case sDenyGroups: while ((arg = strdelim(&cp)) && *arg != '\0') { if (options->num_deny_groups >= MAX_DENY_GROUPS) fatal("%s line %d: too many deny groups.", filename, linenum); options->deny_groups[options->num_deny_groups++] = xstrdup(arg); } break; case sCiphers: arg = strdelim(&cp); if (!arg || *arg == '\0') fatal("%s line %d: Missing argument.", filename, linenum); if (!ciphers_valid(arg)) fatal("%s line %d: Bad SSH2 cipher spec '%s'.", filename, linenum, arg ? arg : ""); if (options->ciphers == NULL) options->ciphers = xstrdup(arg); break; case sMacs: arg = strdelim(&cp); if (!arg || *arg == '\0') fatal("%s line %d: Missing argument.", filename, linenum); if (!mac_valid(arg)) fatal("%s line %d: Bad SSH2 mac spec '%s'.", filename, linenum, arg ? arg : ""); if (options->macs == NULL) options->macs = xstrdup(arg); break; case sKexAlgorithms: arg = strdelim(&cp); if (!arg || *arg == '\0') fatal("%s line %d: Missing argument.", filename, linenum); if (!kex_names_valid(arg)) fatal("%s line %d: Bad SSH2 KexAlgorithms '%s'.", filename, linenum, arg ? arg : ""); if (options->kex_algorithms == NULL) options->kex_algorithms = xstrdup(arg); break; case sProtocol: intptr = &options->protocol; arg = strdelim(&cp); if (!arg || *arg == '\0') fatal("%s line %d: Missing argument.", filename, linenum); value = proto_spec(arg); if (value == SSH_PROTO_UNKNOWN) fatal("%s line %d: Bad protocol spec '%s'.", filename, linenum, arg ? arg : ""); if (*intptr == SSH_PROTO_UNKNOWN) *intptr = value; break; case sSubsystem: if (options->num_subsystems >= MAX_SUBSYSTEMS) { fatal("%s line %d: too many subsystems defined.", filename, linenum); } arg = strdelim(&cp); if (!arg || *arg == '\0') fatal("%s line %d: Missing subsystem name.", filename, linenum); if (!*activep) { arg = strdelim(&cp); break; } for (i = 0; i < options->num_subsystems; i++) if (strcmp(arg, options->subsystem_name[i]) == 0) fatal("%s line %d: Subsystem '%s' already defined.", filename, linenum, arg); options->subsystem_name[options->num_subsystems] = xstrdup(arg); arg = strdelim(&cp); if (!arg || *arg == '\0') fatal("%s line %d: Missing subsystem command.", filename, linenum); options->subsystem_command[options->num_subsystems] = xstrdup(arg); /* Collect arguments (separate to executable) */ p = xstrdup(arg); len = strlen(p) + 1; while ((arg = strdelim(&cp)) != NULL && *arg != '\0') { len += 1 + strlen(arg); p = xrealloc(p, 1, len); strlcat(p, " ", len); strlcat(p, arg, len); } options->subsystem_args[options->num_subsystems] = p; options->num_subsystems++; break; case sMaxStartups: arg = strdelim(&cp); if (!arg || *arg == '\0') fatal("%s line %d: Missing MaxStartups spec.", filename, linenum); if ((n = sscanf(arg, "%d:%d:%d", &options->max_startups_begin, &options->max_startups_rate, &options->max_startups)) == 3) { if (options->max_startups_begin > options->max_startups || options->max_startups_rate > 100 || options->max_startups_rate < 1) fatal("%s line %d: Illegal MaxStartups spec.", filename, linenum); } else if (n != 1) fatal("%s line %d: Illegal MaxStartups spec.", filename, linenum); else options->max_startups = options->max_startups_begin; break; case sMaxAuthTries: intptr = &options->max_authtries; goto parse_int; case sMaxSessions: intptr = &options->max_sessions; goto parse_int; case sBanner: charptr = &options->banner; goto parse_filename; /* * These options can contain %X options expanded at * connect time, so that you can specify paths like: * * AuthorizedKeysFile /etc/ssh_keys/%u */ case sAuthorizedKeysFile: charptr = &options->authorized_keys_file; goto parse_tilde_filename; case sAuthorizedKeysFile2: charptr = &options->authorized_keys_file2; goto parse_tilde_filename; case sAuthorizedPrincipalsFile: charptr = &options->authorized_principals_file; parse_tilde_filename: arg = strdelim(&cp); if (!arg || *arg == '\0') fatal("%s line %d: missing file name.", filename, linenum); if (*activep && *charptr == NULL) { *charptr = tilde_expand_filename(arg, getuid()); /* increase optional counter */ if (intptr != NULL) *intptr = *intptr + 1; } break; case sClientAliveInterval: intptr = &options->client_alive_interval; goto parse_time; case sClientAliveCountMax: intptr = &options->client_alive_count_max; goto parse_int; case sAcceptEnv: while ((arg = strdelim(&cp)) && *arg != '\0') { if (strchr(arg, '=') != NULL) fatal("%s line %d: Invalid environment name.", filename, linenum); if (options->num_accept_env >= MAX_ACCEPT_ENV) fatal("%s line %d: too many allow env.", filename, linenum); if (!*activep) break; options->accept_env[options->num_accept_env++] = xstrdup(arg); } break; case sPermitTunnel: intptr = &options->permit_tun; arg = strdelim(&cp); if (!arg || *arg == '\0') fatal("%s line %d: Missing yes/point-to-point/" "ethernet/no argument.", filename, linenum); value = -1; for (i = 0; tunmode_desc[i].val != -1; i++) if (strcmp(tunmode_desc[i].text, arg) == 0) { value = tunmode_desc[i].val; break; } if (value == -1) fatal("%s line %d: Bad yes/point-to-point/ethernet/" "no argument: %s", filename, linenum, arg); if (*intptr == -1) *intptr = value; break; case sMatch: if (cmdline) fatal("Match directive not supported as a command-line " "option"); value = match_cfg_line(&cp, linenum, user, host, address); if (value < 0) fatal("%s line %d: Bad Match condition", filename, linenum); *activep = value; break; case sPermitOpen: arg = strdelim(&cp); if (!arg || *arg == '\0') fatal("%s line %d: missing PermitOpen specification", filename, linenum); n = options->num_permitted_opens; /* modified later */ if (strcmp(arg, "any") == 0) { if (*activep && n == -1) { channel_clear_adm_permitted_opens(); options->num_permitted_opens = 0; } break; } if (*activep && n == -1) channel_clear_adm_permitted_opens(); for (; arg != NULL && *arg != '\0'; arg = strdelim(&cp)) { p = hpdelim(&arg); if (p == NULL) fatal("%s line %d: missing host in PermitOpen", filename, linenum); p = cleanhostname(p); if (arg == NULL || (port = a2port(arg)) <= 0) fatal("%s line %d: bad port number in " "PermitOpen", filename, linenum); if (*activep && n == -1) options->num_permitted_opens = channel_add_adm_permitted_opens(p, port); } break; case sForceCommand: if (cp == NULL) fatal("%.200s line %d: Missing argument.", filename, linenum); len = strspn(cp, WHITESPACE); if (*activep && options->adm_forced_command == NULL) options->adm_forced_command = xstrdup(cp + len); return 0; case sChrootDirectory: charptr = &options->chroot_directory; arg = strdelim(&cp); if (!arg || *arg == '\0') fatal("%s line %d: missing file name.", filename, linenum); if (*activep && *charptr == NULL) *charptr = xstrdup(arg); break; case sTrustedUserCAKeys: charptr = &options->trusted_user_ca_keys; goto parse_filename; case sRevokedKeys: charptr = &options->revoked_keys_file; goto parse_filename; case sIPQoS: arg = strdelim(&cp); if ((value = parse_ipqos(arg)) == -1) fatal("%s line %d: Bad IPQoS value: %s", filename, linenum, arg); arg = strdelim(&cp); if (arg == NULL) value2 = value; else if ((value2 = parse_ipqos(arg)) == -1) fatal("%s line %d: Bad IPQoS value: %s", filename, linenum, arg); if (*activep) { options->ip_qos_interactive = value; options->ip_qos_bulk = value2; } break; case sVersionAddendum: ssh_version_set_addendum(strtok(cp, "\n")); do { arg = strdelim(&cp); } while (arg != NULL && *arg != '\0'); break; + case sHPNDisabled: + intptr = &options->hpn_disabled; + goto parse_flag; + + case sHPNBufferSize: + intptr = &options->hpn_buffer_size; + goto parse_int; + + case sTcpRcvBufPoll: + intptr = &options->tcp_rcv_buf_poll; + goto parse_flag; + +#ifdef NONE_CIPHER_ENABLED + case sNoneEnabled: + intptr = &options->none_enabled; + goto parse_flag; +#endif + case sDeprecated: logit("%s line %d: Deprecated option %s", filename, linenum, arg); while (arg) arg = strdelim(&cp); break; case sUnsupported: logit("%s line %d: Unsupported option %s", filename, linenum, arg); while (arg) arg = strdelim(&cp); break; default: fatal("%s line %d: Missing handler for opcode %s (%d)", filename, linenum, arg, opcode); } if ((arg = strdelim(&cp)) != NULL && *arg != '\0') fatal("%s line %d: garbage at end of line; \"%.200s\".", filename, linenum, arg); return 0; } /* Reads the server configuration file. */ void load_server_config(const char *filename, Buffer *conf) { char line[1024], *cp; FILE *f; debug2("%s: filename %s", __func__, filename); if ((f = fopen(filename, "r")) == NULL) { perror(filename); exit(1); } buffer_clear(conf); while (fgets(line, sizeof(line), f)) { /* * Trim out comments and strip whitespace * NB - preserve newlines, they are needed to reproduce * line numbers later for error messages */ if ((cp = strchr(line, '#')) != NULL) memcpy(cp, "\n", 2); cp = line + strspn(line, " \t\r"); buffer_append(conf, cp, strlen(cp)); } buffer_append(conf, "\0", 1); fclose(f); debug2("%s: done config len = %d", __func__, buffer_len(conf)); } void parse_server_match_config(ServerOptions *options, const char *user, const char *host, const char *address) { ServerOptions mo; initialize_server_options(&mo); parse_server_config(&mo, "reprocess config", &cfg, user, host, address); copy_set_server_options(options, &mo, 0); } /* Helper macros */ #define M_CP_INTOPT(n) do {\ if (src->n != -1) \ dst->n = src->n; \ } while (0) #define M_CP_STROPT(n) do {\ if (src->n != NULL) { \ if (dst->n != NULL) \ xfree(dst->n); \ dst->n = src->n; \ } \ } while(0) /* * Copy any supported values that are set. * * If the preauth flag is set, we do not bother copying the string or * array values that are not used pre-authentication, because any that we * do use must be explictly sent in mm_getpwnamallow(). */ void copy_set_server_options(ServerOptions *dst, ServerOptions *src, int preauth) { M_CP_INTOPT(password_authentication); M_CP_INTOPT(gss_authentication); M_CP_INTOPT(rsa_authentication); M_CP_INTOPT(pubkey_authentication); M_CP_INTOPT(kerberos_authentication); M_CP_INTOPT(hostbased_authentication); M_CP_INTOPT(hostbased_uses_name_from_packet_only); M_CP_INTOPT(kbd_interactive_authentication); M_CP_INTOPT(zero_knowledge_password_authentication); M_CP_INTOPT(permit_root_login); M_CP_INTOPT(permit_empty_passwd); M_CP_INTOPT(allow_tcp_forwarding); M_CP_INTOPT(allow_agent_forwarding); M_CP_INTOPT(permit_tun); M_CP_INTOPT(gateway_ports); M_CP_INTOPT(x11_display_offset); M_CP_INTOPT(x11_forwarding); M_CP_INTOPT(x11_use_localhost); M_CP_INTOPT(max_sessions); M_CP_INTOPT(max_authtries); M_CP_INTOPT(ip_qos_interactive); M_CP_INTOPT(ip_qos_bulk); M_CP_STROPT(banner); if (preauth) return; M_CP_STROPT(adm_forced_command); M_CP_STROPT(chroot_directory); M_CP_STROPT(trusted_user_ca_keys); M_CP_STROPT(revoked_keys_file); M_CP_STROPT(authorized_keys_file); M_CP_STROPT(authorized_keys_file2); M_CP_STROPT(authorized_principals_file); } #undef M_CP_INTOPT #undef M_CP_STROPT void parse_server_config(ServerOptions *options, const char *filename, Buffer *conf, const char *user, const char *host, const char *address) { int active, linenum, bad_options = 0; char *cp, *obuf, *cbuf; debug2("%s: config %s len %d", __func__, filename, buffer_len(conf)); obuf = cbuf = xstrdup(buffer_ptr(conf)); active = user ? 0 : 1; linenum = 1; while ((cp = strsep(&cbuf, "\n")) != NULL) { if (process_server_config_line(options, cp, filename, linenum++, &active, user, host, address) != 0) bad_options++; } xfree(obuf); if (bad_options > 0) fatal("%s: terminating, %d bad configuration options", filename, bad_options); } static const char * fmt_intarg(ServerOpCodes code, int val) { if (code == sAddressFamily) { switch (val) { case AF_INET: return "inet"; case AF_INET6: return "inet6"; case AF_UNSPEC: return "any"; default: return "UNKNOWN"; } } if (code == sPermitRootLogin) { switch (val) { case PERMIT_NO_PASSWD: return "without-password"; case PERMIT_FORCED_ONLY: return "forced-commands-only"; case PERMIT_YES: return "yes"; } } if (code == sProtocol) { switch (val) { case SSH_PROTO_1: return "1"; case SSH_PROTO_2: return "2"; case (SSH_PROTO_1|SSH_PROTO_2): return "2,1"; default: return "UNKNOWN"; } } if (code == sGatewayPorts && val == 2) return "clientspecified"; if (code == sCompression && val == COMP_DELAYED) return "delayed"; switch (val) { case -1: return "unset"; case 0: return "no"; case 1: return "yes"; } return "UNKNOWN"; } static const char * lookup_opcode_name(ServerOpCodes code) { u_int i; for (i = 0; keywords[i].name != NULL; i++) if (keywords[i].opcode == code) return(keywords[i].name); return "UNKNOWN"; } static void dump_cfg_int(ServerOpCodes code, int val) { printf("%s %d\n", lookup_opcode_name(code), val); } static void dump_cfg_fmtint(ServerOpCodes code, int val) { printf("%s %s\n", lookup_opcode_name(code), fmt_intarg(code, val)); } static void dump_cfg_string(ServerOpCodes code, const char *val) { if (val == NULL) return; printf("%s %s\n", lookup_opcode_name(code), val); } static void dump_cfg_strarray(ServerOpCodes code, u_int count, char **vals) { u_int i; for (i = 0; i < count; i++) printf("%s %s\n", lookup_opcode_name(code), vals[i]); } void dump_config(ServerOptions *o) { u_int i; int ret; struct addrinfo *ai; char addr[NI_MAXHOST], port[NI_MAXSERV], *s = NULL; /* these are usually at the top of the config */ for (i = 0; i < o->num_ports; i++) printf("port %d\n", o->ports[i]); dump_cfg_fmtint(sProtocol, o->protocol); dump_cfg_fmtint(sAddressFamily, o->address_family); /* ListenAddress must be after Port */ for (ai = o->listen_addrs; ai; ai = ai->ai_next) { if ((ret = getnameinfo(ai->ai_addr, ai->ai_addrlen, addr, sizeof(addr), port, sizeof(port), NI_NUMERICHOST|NI_NUMERICSERV)) != 0) { error("getnameinfo failed: %.100s", (ret != EAI_SYSTEM) ? gai_strerror(ret) : strerror(errno)); } else { if (ai->ai_family == AF_INET6) printf("listenaddress [%s]:%s\n", addr, port); else printf("listenaddress %s:%s\n", addr, port); } } /* integer arguments */ #ifdef USE_PAM dump_cfg_int(sUsePAM, o->use_pam); #endif dump_cfg_int(sServerKeyBits, o->server_key_bits); dump_cfg_int(sLoginGraceTime, o->login_grace_time); dump_cfg_int(sKeyRegenerationTime, o->key_regeneration_time); dump_cfg_int(sX11DisplayOffset, o->x11_display_offset); dump_cfg_int(sMaxAuthTries, o->max_authtries); dump_cfg_int(sMaxSessions, o->max_sessions); dump_cfg_int(sClientAliveInterval, o->client_alive_interval); dump_cfg_int(sClientAliveCountMax, o->client_alive_count_max); /* formatted integer arguments */ dump_cfg_fmtint(sPermitRootLogin, o->permit_root_login); dump_cfg_fmtint(sIgnoreRhosts, o->ignore_rhosts); dump_cfg_fmtint(sIgnoreUserKnownHosts, o->ignore_user_known_hosts); dump_cfg_fmtint(sRhostsRSAAuthentication, o->rhosts_rsa_authentication); dump_cfg_fmtint(sHostbasedAuthentication, o->hostbased_authentication); dump_cfg_fmtint(sHostbasedUsesNameFromPacketOnly, o->hostbased_uses_name_from_packet_only); dump_cfg_fmtint(sRSAAuthentication, o->rsa_authentication); dump_cfg_fmtint(sPubkeyAuthentication, o->pubkey_authentication); #ifdef KRB5 dump_cfg_fmtint(sKerberosAuthentication, o->kerberos_authentication); dump_cfg_fmtint(sKerberosOrLocalPasswd, o->kerberos_or_local_passwd); dump_cfg_fmtint(sKerberosTicketCleanup, o->kerberos_ticket_cleanup); # ifdef USE_AFS dump_cfg_fmtint(sKerberosGetAFSToken, o->kerberos_get_afs_token); # endif #endif #ifdef GSSAPI dump_cfg_fmtint(sGssAuthentication, o->gss_authentication); dump_cfg_fmtint(sGssCleanupCreds, o->gss_cleanup_creds); #endif #ifdef JPAKE dump_cfg_fmtint(sZeroKnowledgePasswordAuthentication, o->zero_knowledge_password_authentication); #endif dump_cfg_fmtint(sPasswordAuthentication, o->password_authentication); dump_cfg_fmtint(sKbdInteractiveAuthentication, o->kbd_interactive_authentication); dump_cfg_fmtint(sChallengeResponseAuthentication, o->challenge_response_authentication); dump_cfg_fmtint(sPrintMotd, o->print_motd); dump_cfg_fmtint(sPrintLastLog, o->print_lastlog); dump_cfg_fmtint(sX11Forwarding, o->x11_forwarding); dump_cfg_fmtint(sX11UseLocalhost, o->x11_use_localhost); dump_cfg_fmtint(sStrictModes, o->strict_modes); dump_cfg_fmtint(sTCPKeepAlive, o->tcp_keep_alive); dump_cfg_fmtint(sEmptyPasswd, o->permit_empty_passwd); dump_cfg_fmtint(sPermitUserEnvironment, o->permit_user_env); dump_cfg_fmtint(sUseLogin, o->use_login); dump_cfg_fmtint(sCompression, o->compression); dump_cfg_fmtint(sGatewayPorts, o->gateway_ports); dump_cfg_fmtint(sUseDNS, o->use_dns); dump_cfg_fmtint(sAllowTcpForwarding, o->allow_tcp_forwarding); dump_cfg_fmtint(sUsePrivilegeSeparation, use_privsep); /* string arguments */ dump_cfg_string(sPidFile, o->pid_file); dump_cfg_string(sXAuthLocation, o->xauth_location); dump_cfg_string(sCiphers, o->ciphers); dump_cfg_string(sMacs, o->macs); dump_cfg_string(sBanner, o->banner); dump_cfg_string(sAuthorizedKeysFile, o->authorized_keys_file); dump_cfg_string(sAuthorizedKeysFile2, o->authorized_keys_file2); dump_cfg_string(sForceCommand, o->adm_forced_command); dump_cfg_string(sChrootDirectory, o->chroot_directory); dump_cfg_string(sTrustedUserCAKeys, o->trusted_user_ca_keys); dump_cfg_string(sRevokedKeys, o->revoked_keys_file); dump_cfg_string(sAuthorizedPrincipalsFile, o->authorized_principals_file); /* string arguments requiring a lookup */ dump_cfg_string(sLogLevel, log_level_name(o->log_level)); dump_cfg_string(sLogFacility, log_facility_name(o->log_facility)); /* string array arguments */ dump_cfg_strarray(sHostKeyFile, o->num_host_key_files, o->host_key_files); dump_cfg_strarray(sHostKeyFile, o->num_host_cert_files, o->host_cert_files); dump_cfg_strarray(sAllowUsers, o->num_allow_users, o->allow_users); dump_cfg_strarray(sDenyUsers, o->num_deny_users, o->deny_users); dump_cfg_strarray(sAllowGroups, o->num_allow_groups, o->allow_groups); dump_cfg_strarray(sDenyGroups, o->num_deny_groups, o->deny_groups); dump_cfg_strarray(sAcceptEnv, o->num_accept_env, o->accept_env); /* other arguments */ for (i = 0; i < o->num_subsystems; i++) printf("subsystem %s %s\n", o->subsystem_name[i], o->subsystem_args[i]); printf("maxstartups %d:%d:%d\n", o->max_startups_begin, o->max_startups_rate, o->max_startups); for (i = 0; tunmode_desc[i].val != -1; i++) if (tunmode_desc[i].val == o->permit_tun) { s = tunmode_desc[i].text; break; } dump_cfg_string(sPermitTunnel, s); printf("ipqos 0x%02x 0x%02x\n", o->ip_qos_interactive, o->ip_qos_bulk); channel_print_adm_permitted_opens(); } diff --git a/crypto/openssh/servconf.h b/crypto/openssh/servconf.h index 5a058a416443..f6aadac3a80e 100644 --- a/crypto/openssh/servconf.h +++ b/crypto/openssh/servconf.h @@ -1,178 +1,188 @@ /* $OpenBSD: servconf.h,v 1.95 2010/11/13 23:27:50 djm Exp $ */ +/* $OpenBSD$ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved * Definitions for server configuration data and for the functions reading it. * * As far as I am concerned, the code I have written for this software * can be used freely for any purpose. Any derived versions of this * software must be clearly marked as such, and if the derived work is * incompatible with the protocol description in the RFC file, it must be * called by a name other than "ssh" or "Secure Shell". */ #ifndef SERVCONF_H #define SERVCONF_H #define MAX_PORTS 256 /* Max # ports. */ #define MAX_ALLOW_USERS 256 /* Max # users on allow list. */ #define MAX_DENY_USERS 256 /* Max # users on deny list. */ #define MAX_ALLOW_GROUPS 256 /* Max # groups on allow list. */ #define MAX_DENY_GROUPS 256 /* Max # groups on deny list. */ #define MAX_SUBSYSTEMS 256 /* Max # subsystems. */ #define MAX_HOSTKEYS 256 /* Max # hostkeys. */ #define MAX_HOSTCERTS 256 /* Max # host certificates. */ #define MAX_ACCEPT_ENV 256 /* Max # of env vars. */ #define MAX_MATCH_GROUPS 256 /* Max # of groups for Match. */ /* permit_root_login */ #define PERMIT_NOT_SET -1 #define PERMIT_NO 0 #define PERMIT_FORCED_ONLY 1 #define PERMIT_NO_PASSWD 2 #define PERMIT_YES 3 #define DEFAULT_AUTH_FAIL_MAX 6 /* Default for MaxAuthTries */ #define DEFAULT_SESSIONS_MAX 10 /* Default for MaxSessions */ /* Magic name for internal sftp-server */ #define INTERNAL_SFTP_NAME "internal-sftp" typedef struct { u_int num_ports; u_int ports_from_cmdline; int ports[MAX_PORTS]; /* Port number to listen on. */ char *listen_addr; /* Address on which the server listens. */ struct addrinfo *listen_addrs; /* Addresses on which the server listens. */ int address_family; /* Address family used by the server. */ char *host_key_files[MAX_HOSTKEYS]; /* Files containing host keys. */ int num_host_key_files; /* Number of files for host keys. */ char *host_cert_files[MAX_HOSTCERTS]; /* Files containing host certs. */ int num_host_cert_files; /* Number of files for host certs. */ char *pid_file; /* Where to put our pid */ int server_key_bits;/* Size of the server key. */ int login_grace_time; /* Disconnect if no auth in this time * (sec). */ int key_regeneration_time; /* Server key lifetime (seconds). */ int permit_root_login; /* PERMIT_*, see above */ int ignore_rhosts; /* Ignore .rhosts and .shosts. */ int ignore_user_known_hosts; /* Ignore ~/.ssh/known_hosts * for RhostsRsaAuth */ int print_motd; /* If true, print /etc/motd. */ int print_lastlog; /* If true, print lastlog */ int x11_forwarding; /* If true, permit inet (spoofing) X11 fwd. */ int x11_display_offset; /* What DISPLAY number to start * searching at */ int x11_use_localhost; /* If true, use localhost for fake X11 server. */ char *xauth_location; /* Location of xauth program */ int strict_modes; /* If true, require string home dir modes. */ int tcp_keep_alive; /* If true, set SO_KEEPALIVE. */ int ip_qos_interactive; /* IP ToS/DSCP/class for interactive */ int ip_qos_bulk; /* IP ToS/DSCP/class for bulk traffic */ char *ciphers; /* Supported SSH2 ciphers. */ char *macs; /* Supported SSH2 macs. */ char *kex_algorithms; /* SSH2 kex methods in order of preference. */ int protocol; /* Supported protocol versions. */ int gateway_ports; /* If true, allow remote connects to forwarded ports. */ SyslogFacility log_facility; /* Facility for system logging. */ LogLevel log_level; /* Level for system logging. */ int rhosts_rsa_authentication; /* If true, permit rhosts RSA * authentication. */ int hostbased_authentication; /* If true, permit ssh2 hostbased auth */ int hostbased_uses_name_from_packet_only; /* experimental */ int rsa_authentication; /* If true, permit RSA authentication. */ int pubkey_authentication; /* If true, permit ssh2 pubkey authentication. */ int kerberos_authentication; /* If true, permit Kerberos * authentication. */ int kerberos_or_local_passwd; /* If true, permit kerberos * and any other password * authentication mechanism, * such as SecurID or * /etc/passwd */ int kerberos_ticket_cleanup; /* If true, destroy ticket * file on logout. */ int kerberos_get_afs_token; /* If true, try to get AFS token if * authenticated with Kerberos. */ int gss_authentication; /* If true, permit GSSAPI authentication */ int gss_cleanup_creds; /* If true, destroy cred cache on logout */ int password_authentication; /* If true, permit password * authentication. */ int kbd_interactive_authentication; /* If true, permit */ int challenge_response_authentication; int zero_knowledge_password_authentication; /* If true, permit jpake auth */ int permit_empty_passwd; /* If false, do not permit empty * passwords. */ int permit_user_env; /* If true, read ~/.ssh/environment */ int use_login; /* If true, login(1) is used */ int compression; /* If true, compression is allowed */ int allow_tcp_forwarding; int allow_agent_forwarding; u_int num_allow_users; char *allow_users[MAX_ALLOW_USERS]; u_int num_deny_users; char *deny_users[MAX_DENY_USERS]; u_int num_allow_groups; char *allow_groups[MAX_ALLOW_GROUPS]; u_int num_deny_groups; char *deny_groups[MAX_DENY_GROUPS]; u_int num_subsystems; char *subsystem_name[MAX_SUBSYSTEMS]; char *subsystem_command[MAX_SUBSYSTEMS]; char *subsystem_args[MAX_SUBSYSTEMS]; u_int num_accept_env; char *accept_env[MAX_ACCEPT_ENV]; int max_startups_begin; int max_startups_rate; int max_startups; int max_authtries; int max_sessions; char *banner; /* SSH-2 banner message */ int use_dns; int client_alive_interval; /* * poke the client this often to * see if it's still there */ int client_alive_count_max; /* * If the client is unresponsive * for this many intervals above, * disconnect the session */ char *authorized_keys_file; /* File containing public keys */ char *authorized_keys_file2; char *adm_forced_command; int use_pam; /* Enable auth via PAM */ int permit_tun; int num_permitted_opens; char *chroot_directory; char *revoked_keys_file; char *trusted_user_ca_keys; char *authorized_principals_file; + + int hpn_disabled; /* Disable HPN functionality. */ + int hpn_buffer_size; /* Set HPN buffer size - default 2MB.*/ + int tcp_rcv_buf_poll; /* Poll TCP rcv window in autotuning + * kernels. */ + +#ifdef NONE_CIPHER_ENABLED + int none_enabled; /* Enable NONE cipher switch. */ +#endif } ServerOptions; void initialize_server_options(ServerOptions *); void fill_default_server_options(ServerOptions *); int process_server_config_line(ServerOptions *, char *, const char *, int, int *, const char *, const char *, const char *); void load_server_config(const char *, Buffer *); void parse_server_config(ServerOptions *, const char *, Buffer *, const char *, const char *, const char *); void parse_server_match_config(ServerOptions *, const char *, const char *, const char *); void copy_set_server_options(ServerOptions *, ServerOptions *, int); void dump_config(ServerOptions *); char *derelativise_path(const char *); #endif /* SERVCONF_H */ diff --git a/crypto/openssh/serverloop.c b/crypto/openssh/serverloop.c index 8be01c5c37c4..7606937cca3d 100644 --- a/crypto/openssh/serverloop.c +++ b/crypto/openssh/serverloop.c @@ -1,1254 +1,1263 @@ /* $OpenBSD: serverloop.c,v 1.159 2009/05/28 16:50:16 andreas Exp $ */ +/* $FreeBSD$ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved * Server main loop for handling the interactive session. * * As far as I am concerned, the code I have written for this software * can be used freely for any purpose. Any derived versions of this * software must be clearly marked as such, and if the derived work is * incompatible with the protocol description in the RFC file, it must be * called by a name other than "ssh" or "Secure Shell". * * SSH2 support by Markus Friedl. * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "includes.h" #include #include #include #include #ifdef HAVE_SYS_TIME_H # include #endif #include #include #include #include #include #include #include #include #include #include "openbsd-compat/sys-queue.h" #include "xmalloc.h" #include "packet.h" #include "buffer.h" #include "log.h" #include "servconf.h" #include "canohost.h" #include "sshpty.h" #include "channels.h" #include "compat.h" #include "ssh1.h" #include "ssh2.h" #include "key.h" #include "cipher.h" #include "kex.h" #include "hostfile.h" #include "auth.h" #include "session.h" #include "dispatch.h" #include "auth-options.h" #include "serverloop.h" #include "misc.h" #include "roaming.h" extern ServerOptions options; /* XXX */ extern Kex *xxx_kex; extern Authctxt *the_authctxt; extern int use_privsep; static Buffer stdin_buffer; /* Buffer for stdin data. */ static Buffer stdout_buffer; /* Buffer for stdout data. */ static Buffer stderr_buffer; /* Buffer for stderr data. */ static int fdin; /* Descriptor for stdin (for writing) */ static int fdout; /* Descriptor for stdout (for reading); May be same number as fdin. */ static int fderr; /* Descriptor for stderr. May be -1. */ static long stdin_bytes = 0; /* Number of bytes written to stdin. */ static long stdout_bytes = 0; /* Number of stdout bytes sent to client. */ static long stderr_bytes = 0; /* Number of stderr bytes sent to client. */ static long fdout_bytes = 0; /* Number of stdout bytes read from program. */ static int stdin_eof = 0; /* EOF message received from client. */ static int fdout_eof = 0; /* EOF encountered reading from fdout. */ static int fderr_eof = 0; /* EOF encountered readung from fderr. */ static int fdin_is_tty = 0; /* fdin points to a tty. */ static int connection_in; /* Connection to client (input). */ static int connection_out; /* Connection to client (output). */ static int connection_closed = 0; /* Connection to client closed. */ static u_int buffer_high; /* "Soft" max buffer size. */ static int no_more_sessions = 0; /* Disallow further sessions. */ /* * This SIGCHLD kludge is used to detect when the child exits. The server * will exit after that, as soon as forwarded connections have terminated. */ static volatile sig_atomic_t child_terminated = 0; /* The child has terminated. */ /* Cleanup on signals (!use_privsep case only) */ static volatile sig_atomic_t received_sigterm = 0; /* prototypes */ static void server_init_dispatch(void); /* * we write to this pipe if a SIGCHLD is caught in order to avoid * the race between select() and child_terminated */ static int notify_pipe[2]; static void notify_setup(void) { if (pipe(notify_pipe) < 0) { error("pipe(notify_pipe) failed %s", strerror(errno)); } else if ((fcntl(notify_pipe[0], F_SETFD, 1) == -1) || (fcntl(notify_pipe[1], F_SETFD, 1) == -1)) { error("fcntl(notify_pipe, F_SETFD) failed %s", strerror(errno)); close(notify_pipe[0]); close(notify_pipe[1]); } else { set_nonblock(notify_pipe[0]); set_nonblock(notify_pipe[1]); return; } notify_pipe[0] = -1; /* read end */ notify_pipe[1] = -1; /* write end */ } static void notify_parent(void) { if (notify_pipe[1] != -1) write(notify_pipe[1], "", 1); } static void notify_prepare(fd_set *readset) { if (notify_pipe[0] != -1) FD_SET(notify_pipe[0], readset); } static void notify_done(fd_set *readset) { char c; if (notify_pipe[0] != -1 && FD_ISSET(notify_pipe[0], readset)) while (read(notify_pipe[0], &c, 1) != -1) debug2("notify_done: reading"); } /*ARGSUSED*/ static void sigchld_handler(int sig) { int save_errno = errno; child_terminated = 1; #ifndef _UNICOS mysignal(SIGCHLD, sigchld_handler); #endif notify_parent(); errno = save_errno; } /*ARGSUSED*/ static void sigterm_handler(int sig) { received_sigterm = sig; } /* * Make packets from buffered stderr data, and buffer it for sending * to the client. */ static void make_packets_from_stderr_data(void) { u_int len; /* Send buffered stderr data to the client. */ while (buffer_len(&stderr_buffer) > 0 && packet_not_very_much_data_to_write()) { len = buffer_len(&stderr_buffer); if (packet_is_interactive()) { if (len > 512) len = 512; } else { /* Keep the packets at reasonable size. */ if (len > packet_get_maxsize()) len = packet_get_maxsize(); } packet_start(SSH_SMSG_STDERR_DATA); packet_put_string(buffer_ptr(&stderr_buffer), len); packet_send(); buffer_consume(&stderr_buffer, len); stderr_bytes += len; } } /* * Make packets from buffered stdout data, and buffer it for sending to the * client. */ static void make_packets_from_stdout_data(void) { u_int len; /* Send buffered stdout data to the client. */ while (buffer_len(&stdout_buffer) > 0 && packet_not_very_much_data_to_write()) { len = buffer_len(&stdout_buffer); if (packet_is_interactive()) { if (len > 512) len = 512; } else { /* Keep the packets at reasonable size. */ if (len > packet_get_maxsize()) len = packet_get_maxsize(); } packet_start(SSH_SMSG_STDOUT_DATA); packet_put_string(buffer_ptr(&stdout_buffer), len); packet_send(); buffer_consume(&stdout_buffer, len); stdout_bytes += len; } } static void client_alive_check(void) { int channel_id; /* timeout, check to see how many we have had */ if (packet_inc_alive_timeouts() > options.client_alive_count_max) { logit("Timeout, client not responding."); cleanup_exit(255); } /* * send a bogus global/channel request with "wantreply", * we should get back a failure */ if ((channel_id = channel_find_open()) == -1) { packet_start(SSH2_MSG_GLOBAL_REQUEST); packet_put_cstring("keepalive@openssh.com"); packet_put_char(1); /* boolean: want reply */ } else { channel_request_start(channel_id, "keepalive@openssh.com", 1); } packet_send(); } /* * Sleep in select() until we can do something. This will initialize the * select masks. Upon return, the masks will indicate which descriptors * have data or can accept data. Optionally, a maximum time can be specified * for the duration of the wait (0 = infinite). */ static void wait_until_can_do_something(fd_set **readsetp, fd_set **writesetp, int *maxfdp, u_int *nallocp, u_int max_time_milliseconds) { struct timeval tv, *tvp; int ret; int client_alive_scheduled = 0; int program_alive_scheduled = 0; /* * if using client_alive, set the max timeout accordingly, * and indicate that this particular timeout was for client * alive by setting the client_alive_scheduled flag. * * this could be randomized somewhat to make traffic * analysis more difficult, but we're not doing it yet. */ if (compat20 && max_time_milliseconds == 0 && options.client_alive_interval) { client_alive_scheduled = 1; max_time_milliseconds = options.client_alive_interval * 1000; } /* Allocate and update select() masks for channel descriptors. */ channel_prepare_select(readsetp, writesetp, maxfdp, nallocp, 0); if (compat20) { #if 0 /* wrong: bad condition XXX */ if (channel_not_very_much_buffered_data()) #endif FD_SET(connection_in, *readsetp); } else { /* * Read packets from the client unless we have too much * buffered stdin or channel data. */ if (buffer_len(&stdin_buffer) < buffer_high && channel_not_very_much_buffered_data()) FD_SET(connection_in, *readsetp); /* * If there is not too much data already buffered going to * the client, try to get some more data from the program. */ if (packet_not_very_much_data_to_write()) { program_alive_scheduled = child_terminated; if (!fdout_eof) FD_SET(fdout, *readsetp); if (!fderr_eof) FD_SET(fderr, *readsetp); } /* * If we have buffered data, try to write some of that data * to the program. */ if (fdin != -1 && buffer_len(&stdin_buffer) > 0) FD_SET(fdin, *writesetp); } notify_prepare(*readsetp); /* * If we have buffered packet data going to the client, mark that * descriptor. */ if (packet_have_data_to_write()) FD_SET(connection_out, *writesetp); /* * If child has terminated and there is enough buffer space to read * from it, then read as much as is available and exit. */ if (child_terminated && packet_not_very_much_data_to_write()) if (max_time_milliseconds == 0 || client_alive_scheduled) max_time_milliseconds = 100; if (max_time_milliseconds == 0) tvp = NULL; else { tv.tv_sec = max_time_milliseconds / 1000; tv.tv_usec = 1000 * (max_time_milliseconds % 1000); tvp = &tv; } /* Wait for something to happen, or the timeout to expire. */ ret = select((*maxfdp)+1, *readsetp, *writesetp, NULL, tvp); if (ret == -1) { memset(*readsetp, 0, *nallocp); memset(*writesetp, 0, *nallocp); if (errno != EINTR) error("select: %.100s", strerror(errno)); } else { if (ret == 0 && client_alive_scheduled) client_alive_check(); if (!compat20 && program_alive_scheduled && fdin_is_tty) { if (!fdout_eof) FD_SET(fdout, *readsetp); if (!fderr_eof) FD_SET(fderr, *readsetp); } } notify_done(*readsetp); } /* * Processes input from the client and the program. Input data is stored * in buffers and processed later. */ static void process_input(fd_set *readset) { int len; char buf[16384]; /* Read and buffer any input data from the client. */ if (FD_ISSET(connection_in, readset)) { int cont = 0; len = roaming_read(connection_in, buf, sizeof(buf), &cont); if (len == 0) { if (cont) return; verbose("Connection closed by %.100s", get_remote_ipaddr()); connection_closed = 1; if (compat20) return; cleanup_exit(255); } else if (len < 0) { if (errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK) { verbose("Read error from remote host " "%.100s: %.100s", get_remote_ipaddr(), strerror(errno)); cleanup_exit(255); } } else { /* Buffer any received data. */ packet_process_incoming(buf, len); } } if (compat20) return; /* Read and buffer any available stdout data from the program. */ if (!fdout_eof && FD_ISSET(fdout, readset)) { errno = 0; len = read(fdout, buf, sizeof(buf)); if (len < 0 && (errno == EINTR || ((errno == EAGAIN || errno == EWOULDBLOCK) && !child_terminated))) { /* do nothing */ #ifndef PTY_ZEROREAD } else if (len <= 0) { #else } else if ((!isatty(fdout) && len <= 0) || (isatty(fdout) && (len < 0 || (len == 0 && errno != 0)))) { #endif fdout_eof = 1; } else { buffer_append(&stdout_buffer, buf, len); fdout_bytes += len; } } /* Read and buffer any available stderr data from the program. */ if (!fderr_eof && FD_ISSET(fderr, readset)) { errno = 0; len = read(fderr, buf, sizeof(buf)); if (len < 0 && (errno == EINTR || ((errno == EAGAIN || errno == EWOULDBLOCK) && !child_terminated))) { /* do nothing */ #ifndef PTY_ZEROREAD } else if (len <= 0) { #else } else if ((!isatty(fderr) && len <= 0) || (isatty(fderr) && (len < 0 || (len == 0 && errno != 0)))) { #endif fderr_eof = 1; } else { buffer_append(&stderr_buffer, buf, len); } } } /* * Sends data from internal buffers to client program stdin. */ static void process_output(fd_set *writeset) { struct termios tio; u_char *data; u_int dlen; int len; /* Write buffered data to program stdin. */ if (!compat20 && fdin != -1 && FD_ISSET(fdin, writeset)) { data = buffer_ptr(&stdin_buffer); dlen = buffer_len(&stdin_buffer); len = write(fdin, data, dlen); if (len < 0 && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)) { /* do nothing */ } else if (len <= 0) { if (fdin != fdout) close(fdin); else shutdown(fdin, SHUT_WR); /* We will no longer send. */ fdin = -1; } else { /* Successful write. */ if (fdin_is_tty && dlen >= 1 && data[0] != '\r' && tcgetattr(fdin, &tio) == 0 && !(tio.c_lflag & ECHO) && (tio.c_lflag & ICANON)) { /* * Simulate echo to reduce the impact of * traffic analysis */ packet_send_ignore(len); packet_send(); } /* Consume the data from the buffer. */ buffer_consume(&stdin_buffer, len); /* Update the count of bytes written to the program. */ stdin_bytes += len; } } /* Send any buffered packet data to the client. */ if (FD_ISSET(connection_out, writeset)) packet_write_poll(); } /* * Wait until all buffered output has been sent to the client. * This is used when the program terminates. */ static void drain_output(void) { /* Send any buffered stdout data to the client. */ if (buffer_len(&stdout_buffer) > 0) { packet_start(SSH_SMSG_STDOUT_DATA); packet_put_string(buffer_ptr(&stdout_buffer), buffer_len(&stdout_buffer)); packet_send(); /* Update the count of sent bytes. */ stdout_bytes += buffer_len(&stdout_buffer); } /* Send any buffered stderr data to the client. */ if (buffer_len(&stderr_buffer) > 0) { packet_start(SSH_SMSG_STDERR_DATA); packet_put_string(buffer_ptr(&stderr_buffer), buffer_len(&stderr_buffer)); packet_send(); /* Update the count of sent bytes. */ stderr_bytes += buffer_len(&stderr_buffer); } /* Wait until all buffered data has been written to the client. */ packet_write_wait(); } static void process_buffered_input_packets(void) { dispatch_run(DISPATCH_NONBLOCK, NULL, compat20 ? xxx_kex : NULL); } /* * Performs the interactive session. This handles data transmission between * the client and the program. Note that the notion of stdin, stdout, and * stderr in this function is sort of reversed: this function writes to * stdin (of the child program), and reads from stdout and stderr (of the * child program). */ void server_loop(pid_t pid, int fdin_arg, int fdout_arg, int fderr_arg) { fd_set *readset = NULL, *writeset = NULL; int max_fd = 0; u_int nalloc = 0; int wait_status; /* Status returned by wait(). */ pid_t wait_pid; /* pid returned by wait(). */ int waiting_termination = 0; /* Have displayed waiting close message. */ u_int max_time_milliseconds; u_int previous_stdout_buffer_bytes; u_int stdout_buffer_bytes; int type; debug("Entering interactive session."); /* Initialize the SIGCHLD kludge. */ child_terminated = 0; mysignal(SIGCHLD, sigchld_handler); if (!use_privsep) { signal(SIGTERM, sigterm_handler); signal(SIGINT, sigterm_handler); signal(SIGQUIT, sigterm_handler); } /* Initialize our global variables. */ fdin = fdin_arg; fdout = fdout_arg; fderr = fderr_arg; /* nonblocking IO */ set_nonblock(fdin); set_nonblock(fdout); /* we don't have stderr for interactive terminal sessions, see below */ if (fderr != -1) set_nonblock(fderr); if (!(datafellows & SSH_BUG_IGNOREMSG) && isatty(fdin)) fdin_is_tty = 1; connection_in = packet_get_connection_in(); connection_out = packet_get_connection_out(); notify_setup(); previous_stdout_buffer_bytes = 0; /* Set approximate I/O buffer size. */ if (packet_is_interactive()) buffer_high = 4096; else buffer_high = 64 * 1024; #if 0 /* Initialize max_fd to the maximum of the known file descriptors. */ max_fd = MAX(connection_in, connection_out); max_fd = MAX(max_fd, fdin); max_fd = MAX(max_fd, fdout); if (fderr != -1) max_fd = MAX(max_fd, fderr); #endif /* Initialize Initialize buffers. */ buffer_init(&stdin_buffer); buffer_init(&stdout_buffer); buffer_init(&stderr_buffer); /* * If we have no separate fderr (which is the case when we have a pty * - there we cannot make difference between data sent to stdout and * stderr), indicate that we have seen an EOF from stderr. This way * we don't need to check the descriptor everywhere. */ if (fderr == -1) fderr_eof = 1; server_init_dispatch(); /* Main loop of the server for the interactive session mode. */ for (;;) { /* Process buffered packets from the client. */ process_buffered_input_packets(); /* * If we have received eof, and there is no more pending * input data, cause a real eof by closing fdin. */ if (stdin_eof && fdin != -1 && buffer_len(&stdin_buffer) == 0) { if (fdin != fdout) close(fdin); else shutdown(fdin, SHUT_WR); /* We will no longer send. */ fdin = -1; } /* Make packets from buffered stderr data to send to the client. */ make_packets_from_stderr_data(); /* * Make packets from buffered stdout data to send to the * client. If there is very little to send, this arranges to * not send them now, but to wait a short while to see if we * are getting more data. This is necessary, as some systems * wake up readers from a pty after each separate character. */ max_time_milliseconds = 0; stdout_buffer_bytes = buffer_len(&stdout_buffer); if (stdout_buffer_bytes != 0 && stdout_buffer_bytes < 256 && stdout_buffer_bytes != previous_stdout_buffer_bytes) { /* try again after a while */ max_time_milliseconds = 10; } else { /* Send it now. */ make_packets_from_stdout_data(); } previous_stdout_buffer_bytes = buffer_len(&stdout_buffer); /* Send channel data to the client. */ if (packet_not_very_much_data_to_write()) channel_output_poll(); /* * Bail out of the loop if the program has closed its output * descriptors, and we have no more data to send to the * client, and there is no pending buffered data. */ if (fdout_eof && fderr_eof && !packet_have_data_to_write() && buffer_len(&stdout_buffer) == 0 && buffer_len(&stderr_buffer) == 0) { if (!channel_still_open()) break; if (!waiting_termination) { const char *s = "Waiting for forwarded connections to terminate...\r\n"; char *cp; waiting_termination = 1; buffer_append(&stderr_buffer, s, strlen(s)); /* Display list of open channels. */ cp = channel_open_message(); buffer_append(&stderr_buffer, cp, strlen(cp)); xfree(cp); } } max_fd = MAX(connection_in, connection_out); max_fd = MAX(max_fd, fdin); max_fd = MAX(max_fd, fdout); max_fd = MAX(max_fd, fderr); max_fd = MAX(max_fd, notify_pipe[0]); /* Sleep in select() until we can do something. */ wait_until_can_do_something(&readset, &writeset, &max_fd, &nalloc, max_time_milliseconds); if (received_sigterm) { logit("Exiting on signal %d", received_sigterm); /* Clean up sessions, utmp, etc. */ cleanup_exit(255); } /* Process any channel events. */ channel_after_select(readset, writeset); /* Process input from the client and from program stdout/stderr. */ process_input(readset); /* Process output to the client and to program stdin. */ process_output(writeset); } if (readset) xfree(readset); if (writeset) xfree(writeset); /* Cleanup and termination code. */ /* Wait until all output has been sent to the client. */ drain_output(); - debug("End of interactive session; stdin %ld, stdout (read %ld, sent %ld), stderr %ld bytes.", + debug("End of interactive session; stdin %ld, stdout (read %ld, " "sent %ld), stderr %ld bytes.", stdin_bytes, fdout_bytes, stdout_bytes, stderr_bytes); /* Free and clear the buffers. */ buffer_free(&stdin_buffer); buffer_free(&stdout_buffer); buffer_free(&stderr_buffer); /* Close the file descriptors. */ if (fdout != -1) close(fdout); fdout = -1; fdout_eof = 1; if (fderr != -1) close(fderr); fderr = -1; fderr_eof = 1; if (fdin != -1) close(fdin); fdin = -1; channel_free_all(); /* We no longer want our SIGCHLD handler to be called. */ mysignal(SIGCHLD, SIG_DFL); while ((wait_pid = waitpid(-1, &wait_status, 0)) < 0) if (errno != EINTR) packet_disconnect("wait: %.100s", strerror(errno)); if (wait_pid != pid) error("Strange, wait returned pid %ld, expected %ld", (long)wait_pid, (long)pid); /* Check if it exited normally. */ if (WIFEXITED(wait_status)) { /* Yes, normal exit. Get exit status and send it to the client. */ debug("Command exited with status %d.", WEXITSTATUS(wait_status)); packet_start(SSH_SMSG_EXITSTATUS); packet_put_int(WEXITSTATUS(wait_status)); packet_send(); packet_write_wait(); /* * Wait for exit confirmation. Note that there might be * other packets coming before it; however, the program has * already died so we just ignore them. The client is * supposed to respond with the confirmation when it receives * the exit status. */ do { type = packet_read(); } while (type != SSH_CMSG_EXIT_CONFIRMATION); debug("Received exit confirmation."); return; } /* Check if the program terminated due to a signal. */ if (WIFSIGNALED(wait_status)) packet_disconnect("Command terminated on signal %d.", WTERMSIG(wait_status)); /* Some weird exit cause. Just exit. */ packet_disconnect("wait returned status %04x.", wait_status); /* NOTREACHED */ } static void collect_children(void) { pid_t pid; sigset_t oset, nset; int status; /* block SIGCHLD while we check for dead children */ sigemptyset(&nset); sigaddset(&nset, SIGCHLD); sigprocmask(SIG_BLOCK, &nset, &oset); if (child_terminated) { debug("Received SIGCHLD."); while ((pid = waitpid(-1, &status, WNOHANG)) > 0 || (pid < 0 && errno == EINTR)) if (pid > 0) session_close_by_pid(pid, status); child_terminated = 0; } sigprocmask(SIG_SETMASK, &oset, NULL); } void server_loop2(Authctxt *authctxt) { fd_set *readset = NULL, *writeset = NULL; int rekeying = 0, max_fd, nalloc = 0; debug("Entering interactive session for SSH2."); mysignal(SIGCHLD, sigchld_handler); child_terminated = 0; connection_in = packet_get_connection_in(); connection_out = packet_get_connection_out(); if (!use_privsep) { signal(SIGTERM, sigterm_handler); signal(SIGINT, sigterm_handler); signal(SIGQUIT, sigterm_handler); } notify_setup(); max_fd = MAX(connection_in, connection_out); max_fd = MAX(max_fd, notify_pipe[0]); server_init_dispatch(); for (;;) { process_buffered_input_packets(); rekeying = (xxx_kex != NULL && !xxx_kex->done); if (!rekeying && packet_not_very_much_data_to_write()) channel_output_poll(); wait_until_can_do_something(&readset, &writeset, &max_fd, &nalloc, 0); if (received_sigterm) { logit("Exiting on signal %d", received_sigterm); /* Clean up sessions, utmp, etc. */ cleanup_exit(255); } collect_children(); if (!rekeying) { channel_after_select(readset, writeset); if (packet_need_rekeying()) { debug("need rekeying"); xxx_kex->done = 0; kex_send_kexinit(xxx_kex); } } process_input(readset); if (connection_closed) break; process_output(writeset); } collect_children(); if (readset) xfree(readset); if (writeset) xfree(writeset); /* free all channels, no more reads and writes */ channel_free_all(); /* free remaining sessions, e.g. remove wtmp entries */ session_destroy_all(NULL); } static void server_input_keep_alive(int type, u_int32_t seq, void *ctxt) { debug("Got %d/%u for keepalive", type, seq); /* * reset timeout, since we got a sane answer from the client. * even if this was generated by something other than * the bogus CHANNEL_REQUEST we send for keepalives. */ packet_set_alive_timeouts(0); } static void server_input_stdin_data(int type, u_int32_t seq, void *ctxt) { char *data; u_int data_len; /* Stdin data from the client. Append it to the buffer. */ /* Ignore any data if the client has closed stdin. */ if (fdin == -1) return; data = packet_get_string(&data_len); packet_check_eom(); buffer_append(&stdin_buffer, data, data_len); memset(data, 0, data_len); xfree(data); } static void server_input_eof(int type, u_int32_t seq, void *ctxt) { /* * Eof from the client. The stdin descriptor to the * program will be closed when all buffered data has * drained. */ debug("EOF received for stdin."); packet_check_eom(); stdin_eof = 1; } static void server_input_window_size(int type, u_int32_t seq, void *ctxt) { u_int row = packet_get_int(); u_int col = packet_get_int(); u_int xpixel = packet_get_int(); u_int ypixel = packet_get_int(); debug("Window change received."); packet_check_eom(); if (fdin != -1) pty_change_window_size(fdin, row, col, xpixel, ypixel); } static Channel * server_request_direct_tcpip(void) { Channel *c; char *target, *originator; u_short target_port, originator_port; target = packet_get_string(NULL); target_port = packet_get_int(); originator = packet_get_string(NULL); originator_port = packet_get_int(); packet_check_eom(); debug("server_request_direct_tcpip: originator %s port %d, target %s " "port %d", originator, originator_port, target, target_port); /* XXX check permission */ c = channel_connect_to(target, target_port, "direct-tcpip", "direct-tcpip"); xfree(originator); xfree(target); return c; } static Channel * server_request_tun(void) { Channel *c = NULL; int mode, tun; int sock; mode = packet_get_int(); switch (mode) { case SSH_TUNMODE_POINTOPOINT: case SSH_TUNMODE_ETHERNET: break; default: packet_send_debug("Unsupported tunnel device mode."); return NULL; } if ((options.permit_tun & mode) == 0) { packet_send_debug("Server has rejected tunnel device " "forwarding"); return NULL; } tun = packet_get_int(); if (forced_tun_device != -1) { if (tun != SSH_TUNID_ANY && forced_tun_device != tun) goto done; tun = forced_tun_device; } sock = tun_open(tun, mode); if (sock < 0) goto done; - c = channel_new("tun", SSH_CHANNEL_OPEN, sock, sock, -1, - CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, 0, "tun", 1); + if (options.hpn_disabled) + c = channel_new("tun", SSH_CHANNEL_OPEN, sock, sock, -1, + CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, 0, + "tun", 1); + else + c = channel_new("tun", SSH_CHANNEL_OPEN, sock, sock, -1, + options.hpn_buffer_size, CHAN_TCP_PACKET_DEFAULT, 0, + "tun", 1); c->datagram = 1; #if defined(SSH_TUN_FILTER) if (mode == SSH_TUNMODE_POINTOPOINT) channel_register_filter(c->self, sys_tun_infilter, sys_tun_outfilter, NULL, NULL); #endif done: if (c == NULL) packet_send_debug("Failed to open the tunnel device."); return c; } static Channel * server_request_session(void) { Channel *c; debug("input_session_request"); packet_check_eom(); if (no_more_sessions) { packet_disconnect("Possible attack: attempt to open a session " "after additional sessions disabled"); } /* * A server session has no fd to read or write until a * CHANNEL_REQUEST for a shell is made, so we set the type to * SSH_CHANNEL_LARVAL. Additionally, a callback for handling all * CHANNEL_REQUEST messages is registered. */ c = channel_new("session", SSH_CHANNEL_LARVAL, -1, -1, -1, /*window size*/0, CHAN_SES_PACKET_DEFAULT, 0, "server-session", 1); + if (!options.hpn_disabled && options.tcp_rcv_buf_poll) + c->dynamic_window = 1; if (session_open(the_authctxt, c->self) != 1) { debug("session open failed, free channel %d", c->self); channel_free(c); return NULL; } channel_register_cleanup(c->self, session_close_by_channel, 0); return c; } static void server_input_channel_open(int type, u_int32_t seq, void *ctxt) { Channel *c = NULL; char *ctype; int rchan; u_int rmaxpack, rwindow, len; ctype = packet_get_string(&len); rchan = packet_get_int(); rwindow = packet_get_int(); rmaxpack = packet_get_int(); debug("server_input_channel_open: ctype %s rchan %d win %d max %d", ctype, rchan, rwindow, rmaxpack); if (strcmp(ctype, "session") == 0) { c = server_request_session(); } else if (strcmp(ctype, "direct-tcpip") == 0) { c = server_request_direct_tcpip(); } else if (strcmp(ctype, "tun@openssh.com") == 0) { c = server_request_tun(); } if (c != NULL) { debug("server_input_channel_open: confirm %s", ctype); c->remote_id = rchan; c->remote_window = rwindow; c->remote_maxpacket = rmaxpack; if (c->type != SSH_CHANNEL_CONNECTING) { packet_start(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION); packet_put_int(c->remote_id); packet_put_int(c->self); packet_put_int(c->local_window); packet_put_int(c->local_maxpacket); packet_send(); } } else { debug("server_input_channel_open: failure %s", ctype); packet_start(SSH2_MSG_CHANNEL_OPEN_FAILURE); packet_put_int(rchan); packet_put_int(SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED); if (!(datafellows & SSH_BUG_OPENFAILURE)) { packet_put_cstring("open failed"); packet_put_cstring(""); } packet_send(); } xfree(ctype); } static void server_input_global_request(int type, u_int32_t seq, void *ctxt) { char *rtype; int want_reply; int success = 0, allocated_listen_port = 0; rtype = packet_get_string(NULL); want_reply = packet_get_char(); debug("server_input_global_request: rtype %s want_reply %d", rtype, want_reply); /* -R style forwarding */ if (strcmp(rtype, "tcpip-forward") == 0) { struct passwd *pw; char *listen_address; u_short listen_port; pw = the_authctxt->pw; if (pw == NULL || !the_authctxt->valid) fatal("server_input_global_request: no/invalid user"); listen_address = packet_get_string(NULL); listen_port = (u_short)packet_get_int(); debug("server_input_global_request: tcpip-forward listen %s port %d", listen_address, listen_port); /* check permissions */ if (!options.allow_tcp_forwarding || no_port_forwarding_flag || (!want_reply && listen_port == 0) #ifndef NO_IPPORT_RESERVED_CONCEPT || (listen_port != 0 && listen_port < IPPORT_RESERVED && pw->pw_uid != 0) #endif ) { success = 0; packet_send_debug("Server has disabled port forwarding."); } else { /* Start listening on the port */ success = channel_setup_remote_fwd_listener( listen_address, listen_port, &allocated_listen_port, options.gateway_ports); } xfree(listen_address); } else if (strcmp(rtype, "cancel-tcpip-forward") == 0) { char *cancel_address; u_short cancel_port; cancel_address = packet_get_string(NULL); cancel_port = (u_short)packet_get_int(); debug("%s: cancel-tcpip-forward addr %s port %d", __func__, cancel_address, cancel_port); success = channel_cancel_rport_listener(cancel_address, cancel_port); xfree(cancel_address); } else if (strcmp(rtype, "no-more-sessions@openssh.com") == 0) { no_more_sessions = 1; success = 1; } if (want_reply) { packet_start(success ? SSH2_MSG_REQUEST_SUCCESS : SSH2_MSG_REQUEST_FAILURE); if (success && allocated_listen_port > 0) packet_put_int(allocated_listen_port); packet_send(); packet_write_wait(); } xfree(rtype); } static void server_input_channel_req(int type, u_int32_t seq, void *ctxt) { Channel *c; int id, reply, success = 0; char *rtype; id = packet_get_int(); rtype = packet_get_string(NULL); reply = packet_get_char(); debug("server_input_channel_req: channel %d request %s reply %d", id, rtype, reply); if ((c = channel_lookup(id)) == NULL) packet_disconnect("server_input_channel_req: " "unknown channel %d", id); if (!strcmp(rtype, "eow@openssh.com")) { packet_check_eom(); chan_rcvd_eow(c); } else if ((c->type == SSH_CHANNEL_LARVAL || c->type == SSH_CHANNEL_OPEN) && strcmp(c->ctype, "session") == 0) success = session_input_channel_req(c, rtype); if (reply) { packet_start(success ? SSH2_MSG_CHANNEL_SUCCESS : SSH2_MSG_CHANNEL_FAILURE); packet_put_int(c->remote_id); packet_send(); } xfree(rtype); } static void server_init_dispatch_20(void) { debug("server_init_dispatch_20"); dispatch_init(&dispatch_protocol_error); dispatch_set(SSH2_MSG_CHANNEL_CLOSE, &channel_input_oclose); dispatch_set(SSH2_MSG_CHANNEL_DATA, &channel_input_data); dispatch_set(SSH2_MSG_CHANNEL_EOF, &channel_input_ieof); dispatch_set(SSH2_MSG_CHANNEL_EXTENDED_DATA, &channel_input_extended_data); dispatch_set(SSH2_MSG_CHANNEL_OPEN, &server_input_channel_open); dispatch_set(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION, &channel_input_open_confirmation); dispatch_set(SSH2_MSG_CHANNEL_OPEN_FAILURE, &channel_input_open_failure); dispatch_set(SSH2_MSG_CHANNEL_REQUEST, &server_input_channel_req); dispatch_set(SSH2_MSG_CHANNEL_WINDOW_ADJUST, &channel_input_window_adjust); dispatch_set(SSH2_MSG_GLOBAL_REQUEST, &server_input_global_request); /* client_alive */ dispatch_set(SSH2_MSG_CHANNEL_SUCCESS, &server_input_keep_alive); dispatch_set(SSH2_MSG_CHANNEL_FAILURE, &server_input_keep_alive); dispatch_set(SSH2_MSG_REQUEST_SUCCESS, &server_input_keep_alive); dispatch_set(SSH2_MSG_REQUEST_FAILURE, &server_input_keep_alive); /* rekeying */ dispatch_set(SSH2_MSG_KEXINIT, &kex_input_kexinit); } static void server_init_dispatch_13(void) { debug("server_init_dispatch_13"); dispatch_init(NULL); dispatch_set(SSH_CMSG_EOF, &server_input_eof); dispatch_set(SSH_CMSG_STDIN_DATA, &server_input_stdin_data); dispatch_set(SSH_CMSG_WINDOW_SIZE, &server_input_window_size); dispatch_set(SSH_MSG_CHANNEL_CLOSE, &channel_input_close); dispatch_set(SSH_MSG_CHANNEL_CLOSE_CONFIRMATION, &channel_input_close_confirmation); dispatch_set(SSH_MSG_CHANNEL_DATA, &channel_input_data); dispatch_set(SSH_MSG_CHANNEL_OPEN_CONFIRMATION, &channel_input_open_confirmation); dispatch_set(SSH_MSG_CHANNEL_OPEN_FAILURE, &channel_input_open_failure); dispatch_set(SSH_MSG_PORT_OPEN, &channel_input_port_open); } static void server_init_dispatch_15(void) { server_init_dispatch_13(); debug("server_init_dispatch_15"); dispatch_set(SSH_MSG_CHANNEL_CLOSE, &channel_input_ieof); dispatch_set(SSH_MSG_CHANNEL_CLOSE_CONFIRMATION, &channel_input_oclose); } static void server_init_dispatch(void) { if (compat20) server_init_dispatch_20(); else if (compat13) server_init_dispatch_13(); else server_init_dispatch_15(); } diff --git a/crypto/openssh/session.c b/crypto/openssh/session.c index 242a86190f2e..b04e629974b1 100644 --- a/crypto/openssh/session.c +++ b/crypto/openssh/session.c @@ -1,2739 +1,2747 @@ /* $OpenBSD: session.c,v 1.258 2010/11/25 04:10:09 djm Exp $ */ +/* $FreeBSD$ */ /* * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved * * As far as I am concerned, the code I have written for this software * can be used freely for any purpose. Any derived versions of this * software must be clearly marked as such, and if the derived work is * incompatible with the protocol description in the RFC file, it must be * called by a name other than "ssh" or "Secure Shell". * * SSH2 support by Markus Friedl. * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "includes.h" __RCSID("$FreeBSD$"); #include #include #ifdef HAVE_SYS_STAT_H # include #endif #include #include #include #include #include #include #include #ifdef HAVE_PATHS_H #include #endif #include #include #include #include #include #include #include #include "openbsd-compat/sys-queue.h" #include "xmalloc.h" #include "ssh.h" #include "ssh1.h" #include "ssh2.h" #include "sshpty.h" #include "packet.h" #include "buffer.h" #include "match.h" #include "uidswap.h" #include "compat.h" #include "channels.h" #include "key.h" #include "cipher.h" #ifdef GSSAPI #include "ssh-gss.h" #endif #include "hostfile.h" #include "auth.h" #include "auth-options.h" #include "pathnames.h" #include "log.h" #include "servconf.h" #include "sshlogin.h" #include "serverloop.h" #include "canohost.h" #include "misc.h" #include "session.h" #include "kex.h" #include "monitor_wrap.h" #include "sftp.h" #if defined(KRB5) && defined(USE_AFS) #include #endif #define IS_INTERNAL_SFTP(c) \ (!strncmp(c, INTERNAL_SFTP_NAME, sizeof(INTERNAL_SFTP_NAME) - 1) && \ (c[sizeof(INTERNAL_SFTP_NAME) - 1] == '\0' || \ c[sizeof(INTERNAL_SFTP_NAME) - 1] == ' ' || \ c[sizeof(INTERNAL_SFTP_NAME) - 1] == '\t')) /* func */ Session *session_new(void); void session_set_fds(Session *, int, int, int, int, int); void session_pty_cleanup(Session *); void session_proctitle(Session *); int session_setup_x11fwd(Session *); int do_exec_pty(Session *, const char *); int do_exec_no_pty(Session *, const char *); int do_exec(Session *, const char *); void do_login(Session *, const char *); #ifdef LOGIN_NEEDS_UTMPX static void do_pre_login(Session *s); #endif void do_child(Session *, const char *); void do_motd(void); int check_quietlogin(Session *, const char *); static void do_authenticated1(Authctxt *); static void do_authenticated2(Authctxt *); static int session_pty_req(Session *); /* import */ extern ServerOptions options; extern char *__progname; extern int log_stderr; extern int debug_flag; extern u_int utmp_len; extern int startup_pipe; extern void destroy_sensitive_data(void); extern Buffer loginmsg; /* original command from peer. */ const char *original_command = NULL; /* data */ static int sessions_first_unused = -1; static int sessions_nalloc = 0; static Session *sessions = NULL; #define SUBSYSTEM_NONE 0 #define SUBSYSTEM_EXT 1 #define SUBSYSTEM_INT_SFTP 2 #define SUBSYSTEM_INT_SFTP_ERROR 3 #ifdef HAVE_LOGIN_CAP login_cap_t *lc; #endif static int is_child = 0; /* Name and directory of socket for authentication agent forwarding. */ static char *auth_sock_name = NULL; static char *auth_sock_dir = NULL; /* removes the agent forwarding socket */ static void auth_sock_cleanup_proc(struct passwd *pw) { if (auth_sock_name != NULL) { temporarily_use_uid(pw); unlink(auth_sock_name); rmdir(auth_sock_dir); auth_sock_name = NULL; restore_uid(); } } static int auth_input_request_forwarding(struct passwd * pw) { Channel *nc; int sock = -1; struct sockaddr_un sunaddr; if (auth_sock_name != NULL) { error("authentication forwarding requested twice."); return 0; } /* Temporarily drop privileged uid for mkdir/bind. */ temporarily_use_uid(pw); /* Allocate a buffer for the socket name, and format the name. */ auth_sock_dir = xstrdup("/tmp/ssh-XXXXXXXXXX"); /* Create private directory for socket */ if (mkdtemp(auth_sock_dir) == NULL) { packet_send_debug("Agent forwarding disabled: " "mkdtemp() failed: %.100s", strerror(errno)); restore_uid(); xfree(auth_sock_dir); auth_sock_dir = NULL; goto authsock_err; } xasprintf(&auth_sock_name, "%s/agent.%ld", auth_sock_dir, (long) getpid()); /* Create the socket. */ sock = socket(AF_UNIX, SOCK_STREAM, 0); if (sock < 0) { error("socket: %.100s", strerror(errno)); restore_uid(); goto authsock_err; } /* Bind it to the name. */ memset(&sunaddr, 0, sizeof(sunaddr)); sunaddr.sun_family = AF_UNIX; strlcpy(sunaddr.sun_path, auth_sock_name, sizeof(sunaddr.sun_path)); if (bind(sock, (struct sockaddr *)&sunaddr, sizeof(sunaddr)) < 0) { error("bind: %.100s", strerror(errno)); restore_uid(); goto authsock_err; } /* Restore the privileged uid. */ restore_uid(); /* Start listening on the socket. */ if (listen(sock, SSH_LISTEN_BACKLOG) < 0) { error("listen: %.100s", strerror(errno)); goto authsock_err; } - /* Allocate a channel for the authentication agent socket. */ + /* + * Allocate a channel for the authentication agent socket. + * Ignore HPN on that one given no improvement expected. + */ nc = channel_new("auth socket", SSH_CHANNEL_AUTH_SOCKET, sock, sock, -1, CHAN_X11_WINDOW_DEFAULT, CHAN_X11_PACKET_DEFAULT, 0, "auth socket", 1); nc->path = xstrdup(auth_sock_name); return 1; authsock_err: if (auth_sock_name != NULL) xfree(auth_sock_name); if (auth_sock_dir != NULL) { rmdir(auth_sock_dir); xfree(auth_sock_dir); } if (sock != -1) close(sock); auth_sock_name = NULL; auth_sock_dir = NULL; return 0; } static void display_loginmsg(void) { if (buffer_len(&loginmsg) > 0) { buffer_append(&loginmsg, "\0", 1); printf("%s", (char *)buffer_ptr(&loginmsg)); buffer_clear(&loginmsg); } } void do_authenticated(Authctxt *authctxt) { setproctitle("%s", authctxt->pw->pw_name); /* setup the channel layer */ if (!no_port_forwarding_flag && options.allow_tcp_forwarding) channel_permit_all_opens(); auth_debug_send(); if (compat20) do_authenticated2(authctxt); else do_authenticated1(authctxt); do_cleanup(authctxt); } /* * Prepares for an interactive session. This is called after the user has * been successfully authenticated. During this message exchange, pseudo * terminals are allocated, X11, TCP/IP, and authentication agent forwardings * are requested, etc. */ static void do_authenticated1(Authctxt *authctxt) { Session *s; char *command; int success, type, screen_flag; int enable_compression_after_reply = 0; u_int proto_len, data_len, dlen, compression_level = 0; s = session_new(); if (s == NULL) { error("no more sessions"); return; } s->authctxt = authctxt; s->pw = authctxt->pw; /* * We stay in this loop until the client requests to execute a shell * or a command. */ for (;;) { success = 0; /* Get a packet from the client. */ type = packet_read(); /* Process the packet. */ switch (type) { case SSH_CMSG_REQUEST_COMPRESSION: compression_level = packet_get_int(); packet_check_eom(); if (compression_level < 1 || compression_level > 9) { packet_send_debug("Received invalid compression level %d.", compression_level); break; } if (options.compression == COMP_NONE) { debug2("compression disabled"); break; } /* Enable compression after we have responded with SUCCESS. */ enable_compression_after_reply = 1; success = 1; break; case SSH_CMSG_REQUEST_PTY: success = session_pty_req(s); break; case SSH_CMSG_X11_REQUEST_FORWARDING: s->auth_proto = packet_get_string(&proto_len); s->auth_data = packet_get_string(&data_len); screen_flag = packet_get_protocol_flags() & SSH_PROTOFLAG_SCREEN_NUMBER; debug2("SSH_PROTOFLAG_SCREEN_NUMBER: %d", screen_flag); if (packet_remaining() == 4) { if (!screen_flag) debug2("Buggy client: " "X11 screen flag missing"); s->screen = packet_get_int(); } else { s->screen = 0; } packet_check_eom(); success = session_setup_x11fwd(s); if (!success) { xfree(s->auth_proto); xfree(s->auth_data); s->auth_proto = NULL; s->auth_data = NULL; } break; case SSH_CMSG_AGENT_REQUEST_FORWARDING: if (!options.allow_agent_forwarding || no_agent_forwarding_flag || compat13) { debug("Authentication agent forwarding not permitted for this authentication."); break; } debug("Received authentication agent forwarding request."); success = auth_input_request_forwarding(s->pw); break; case SSH_CMSG_PORT_FORWARD_REQUEST: if (no_port_forwarding_flag) { debug("Port forwarding not permitted for this authentication."); break; } if (!options.allow_tcp_forwarding) { debug("Port forwarding not permitted."); break; } debug("Received TCP/IP port forwarding request."); if (channel_input_port_forward_request(s->pw->pw_uid == 0, options.gateway_ports) < 0) { debug("Port forwarding failed."); break; } success = 1; break; case SSH_CMSG_MAX_PACKET_SIZE: if (packet_set_maxsize(packet_get_int()) > 0) success = 1; break; case SSH_CMSG_EXEC_SHELL: case SSH_CMSG_EXEC_CMD: if (type == SSH_CMSG_EXEC_CMD) { command = packet_get_string(&dlen); debug("Exec command '%.500s'", command); if (do_exec(s, command) != 0) packet_disconnect( "command execution failed"); xfree(command); } else { if (do_exec(s, NULL) != 0) packet_disconnect( "shell execution failed"); } packet_check_eom(); session_close(s); return; default: /* * Any unknown messages in this phase are ignored, * and a failure message is returned. */ logit("Unknown packet type received after authentication: %d", type); } packet_start(success ? SSH_SMSG_SUCCESS : SSH_SMSG_FAILURE); packet_send(); packet_write_wait(); /* Enable compression now that we have replied if appropriate. */ if (enable_compression_after_reply) { enable_compression_after_reply = 0; packet_start_compression(compression_level); } } } #define USE_PIPES /* * This is called to fork and execute a command when we have no tty. This * will call do_child from the child, and server_loop from the parent after * setting up file descriptors and such. */ int do_exec_no_pty(Session *s, const char *command) { pid_t pid; #ifdef USE_PIPES int pin[2], pout[2], perr[2]; if (s == NULL) fatal("do_exec_no_pty: no session"); /* Allocate pipes for communicating with the program. */ if (pipe(pin) < 0) { error("%s: pipe in: %.100s", __func__, strerror(errno)); return -1; } if (pipe(pout) < 0) { error("%s: pipe out: %.100s", __func__, strerror(errno)); close(pin[0]); close(pin[1]); return -1; } if (pipe(perr) < 0) { error("%s: pipe err: %.100s", __func__, strerror(errno)); close(pin[0]); close(pin[1]); close(pout[0]); close(pout[1]); return -1; } #else int inout[2], err[2]; if (s == NULL) fatal("do_exec_no_pty: no session"); /* Uses socket pairs to communicate with the program. */ if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) < 0) { error("%s: socketpair #1: %.100s", __func__, strerror(errno)); return -1; } if (socketpair(AF_UNIX, SOCK_STREAM, 0, err) < 0) { error("%s: socketpair #2: %.100s", __func__, strerror(errno)); close(inout[0]); close(inout[1]); return -1; } #endif session_proctitle(s); /* Fork the child. */ switch ((pid = fork())) { case -1: error("%s: fork: %.100s", __func__, strerror(errno)); #ifdef USE_PIPES close(pin[0]); close(pin[1]); close(pout[0]); close(pout[1]); close(perr[0]); close(perr[1]); #else close(inout[0]); close(inout[1]); close(err[0]); close(err[1]); #endif return -1; case 0: is_child = 1; /* Child. Reinitialize the log since the pid has changed. */ log_init(__progname, options.log_level, options.log_facility, log_stderr); /* * Create a new session and process group since the 4.4BSD * setlogin() affects the entire process group. */ if (setsid() < 0) error("setsid failed: %.100s", strerror(errno)); #ifdef USE_PIPES /* * Redirect stdin. We close the parent side of the socket * pair, and make the child side the standard input. */ close(pin[1]); if (dup2(pin[0], 0) < 0) perror("dup2 stdin"); close(pin[0]); /* Redirect stdout. */ close(pout[0]); if (dup2(pout[1], 1) < 0) perror("dup2 stdout"); close(pout[1]); /* Redirect stderr. */ close(perr[0]); if (dup2(perr[1], 2) < 0) perror("dup2 stderr"); close(perr[1]); #else /* * Redirect stdin, stdout, and stderr. Stdin and stdout will * use the same socket, as some programs (particularly rdist) * seem to depend on it. */ close(inout[1]); close(err[1]); if (dup2(inout[0], 0) < 0) /* stdin */ perror("dup2 stdin"); if (dup2(inout[0], 1) < 0) /* stdout (same as stdin) */ perror("dup2 stdout"); close(inout[0]); if (dup2(err[0], 2) < 0) /* stderr */ perror("dup2 stderr"); close(err[0]); #endif #ifdef _UNICOS cray_init_job(s->pw); /* set up cray jid and tmpdir */ #endif /* Do processing for the child (exec command etc). */ do_child(s, command); /* NOTREACHED */ default: break; } #ifdef _UNICOS signal(WJSIGNAL, cray_job_termination_handler); #endif /* _UNICOS */ #ifdef HAVE_CYGWIN cygwin_set_impersonation_token(INVALID_HANDLE_VALUE); #endif s->pid = pid; /* Set interactive/non-interactive mode. */ packet_set_interactive(s->display != NULL, options.ip_qos_interactive, options.ip_qos_bulk); /* * Clear loginmsg, since it's the child's responsibility to display * it to the user, otherwise multiple sessions may accumulate * multiple copies of the login messages. */ buffer_clear(&loginmsg); #ifdef USE_PIPES /* We are the parent. Close the child sides of the pipes. */ close(pin[0]); close(pout[1]); close(perr[1]); if (compat20) { session_set_fds(s, pin[1], pout[0], perr[0], s->is_subsystem, 0); } else { /* Enter the interactive session. */ server_loop(pid, pin[1], pout[0], perr[0]); /* server_loop has closed pin[1], pout[0], and perr[0]. */ } #else /* We are the parent. Close the child sides of the socket pairs. */ close(inout[0]); close(err[0]); /* * Enter the interactive session. Note: server_loop must be able to * handle the case that fdin and fdout are the same. */ if (compat20) { session_set_fds(s, inout[1], inout[1], err[1], s->is_subsystem, 0); } else { server_loop(pid, inout[1], inout[1], err[1]); /* server_loop has closed inout[1] and err[1]. */ } #endif return 0; } /* * This is called to fork and execute a command when we have a tty. This * will call do_child from the child, and server_loop from the parent after * setting up file descriptors, controlling tty, updating wtmp, utmp, * lastlog, and other such operations. */ int do_exec_pty(Session *s, const char *command) { int fdout, ptyfd, ttyfd, ptymaster; pid_t pid; if (s == NULL) fatal("do_exec_pty: no session"); ptyfd = s->ptyfd; ttyfd = s->ttyfd; /* * Create another descriptor of the pty master side for use as the * standard input. We could use the original descriptor, but this * simplifies code in server_loop. The descriptor is bidirectional. * Do this before forking (and cleanup in the child) so as to * detect and gracefully fail out-of-fd conditions. */ if ((fdout = dup(ptyfd)) < 0) { error("%s: dup #1: %s", __func__, strerror(errno)); close(ttyfd); close(ptyfd); return -1; } /* we keep a reference to the pty master */ if ((ptymaster = dup(ptyfd)) < 0) { error("%s: dup #2: %s", __func__, strerror(errno)); close(ttyfd); close(ptyfd); close(fdout); return -1; } /* Fork the child. */ switch ((pid = fork())) { case -1: error("%s: fork: %.100s", __func__, strerror(errno)); close(fdout); close(ptymaster); close(ttyfd); close(ptyfd); return -1; case 0: is_child = 1; close(fdout); close(ptymaster); /* Child. Reinitialize the log because the pid has changed. */ log_init(__progname, options.log_level, options.log_facility, log_stderr); /* Close the master side of the pseudo tty. */ close(ptyfd); /* Make the pseudo tty our controlling tty. */ pty_make_controlling_tty(&ttyfd, s->tty); /* Redirect stdin/stdout/stderr from the pseudo tty. */ if (dup2(ttyfd, 0) < 0) error("dup2 stdin: %s", strerror(errno)); if (dup2(ttyfd, 1) < 0) error("dup2 stdout: %s", strerror(errno)); if (dup2(ttyfd, 2) < 0) error("dup2 stderr: %s", strerror(errno)); /* Close the extra descriptor for the pseudo tty. */ close(ttyfd); /* record login, etc. similar to login(1) */ #ifndef HAVE_OSF_SIA if (!(options.use_login && command == NULL)) { #ifdef _UNICOS cray_init_job(s->pw); /* set up cray jid and tmpdir */ #endif /* _UNICOS */ do_login(s, command); } # ifdef LOGIN_NEEDS_UTMPX else do_pre_login(s); # endif #endif /* * Do common processing for the child, such as execing * the command. */ do_child(s, command); /* NOTREACHED */ default: break; } #ifdef _UNICOS signal(WJSIGNAL, cray_job_termination_handler); #endif /* _UNICOS */ #ifdef HAVE_CYGWIN cygwin_set_impersonation_token(INVALID_HANDLE_VALUE); #endif s->pid = pid; /* Parent. Close the slave side of the pseudo tty. */ close(ttyfd); /* Enter interactive session. */ s->ptymaster = ptymaster; packet_set_interactive(1, options.ip_qos_interactive, options.ip_qos_bulk); if (compat20) { session_set_fds(s, ptyfd, fdout, -1, 1, 1); } else { server_loop(pid, ptyfd, fdout, -1); /* server_loop _has_ closed ptyfd and fdout. */ } return 0; } #ifdef LOGIN_NEEDS_UTMPX static void do_pre_login(Session *s) { socklen_t fromlen; struct sockaddr_storage from; pid_t pid = getpid(); /* * Get IP address of client. If the connection is not a socket, let * the address be 0.0.0.0. */ memset(&from, 0, sizeof(from)); fromlen = sizeof(from); if (packet_connection_is_on_socket()) { if (getpeername(packet_get_connection_in(), (struct sockaddr *)&from, &fromlen) < 0) { debug("getpeername: %.100s", strerror(errno)); cleanup_exit(255); } } record_utmp_only(pid, s->tty, s->pw->pw_name, get_remote_name_or_ip(utmp_len, options.use_dns), (struct sockaddr *)&from, fromlen); } #endif /* * This is called to fork and execute a command. If another command is * to be forced, execute that instead. */ int do_exec(Session *s, const char *command) { int ret; if (options.adm_forced_command) { original_command = command; command = options.adm_forced_command; if (IS_INTERNAL_SFTP(command)) { s->is_subsystem = s->is_subsystem ? SUBSYSTEM_INT_SFTP : SUBSYSTEM_INT_SFTP_ERROR; } else if (s->is_subsystem) s->is_subsystem = SUBSYSTEM_EXT; debug("Forced command (config) '%.900s'", command); } else if (forced_command) { original_command = command; command = forced_command; if (IS_INTERNAL_SFTP(command)) { s->is_subsystem = s->is_subsystem ? SUBSYSTEM_INT_SFTP : SUBSYSTEM_INT_SFTP_ERROR; } else if (s->is_subsystem) s->is_subsystem = SUBSYSTEM_EXT; debug("Forced command (key option) '%.900s'", command); } #ifdef SSH_AUDIT_EVENTS if (command != NULL) PRIVSEP(audit_run_command(command)); else if (s->ttyfd == -1) { char *shell = s->pw->pw_shell; if (shell[0] == '\0') /* empty shell means /bin/sh */ shell =_PATH_BSHELL; PRIVSEP(audit_run_command(shell)); } #endif if (s->ttyfd != -1) ret = do_exec_pty(s, command); else ret = do_exec_no_pty(s, command); original_command = NULL; /* * Clear loginmsg: it's the child's responsibility to display * it to the user, otherwise multiple sessions may accumulate * multiple copies of the login messages. */ buffer_clear(&loginmsg); return ret; } /* administrative, login(1)-like work */ void do_login(Session *s, const char *command) { socklen_t fromlen; struct sockaddr_storage from; struct passwd * pw = s->pw; pid_t pid = getpid(); /* * Get IP address of client. If the connection is not a socket, let * the address be 0.0.0.0. */ memset(&from, 0, sizeof(from)); fromlen = sizeof(from); if (packet_connection_is_on_socket()) { if (getpeername(packet_get_connection_in(), (struct sockaddr *)&from, &fromlen) < 0) { debug("getpeername: %.100s", strerror(errno)); cleanup_exit(255); } } /* Record that there was a login on that tty from the remote host. */ if (!use_privsep) record_login(pid, s->tty, pw->pw_name, pw->pw_uid, get_remote_name_or_ip(utmp_len, options.use_dns), (struct sockaddr *)&from, fromlen); #ifdef USE_PAM /* * If password change is needed, do it now. * This needs to occur before the ~/.hushlogin check. */ if (options.use_pam && !use_privsep && s->authctxt->force_pwchange) { display_loginmsg(); do_pam_chauthtok(); s->authctxt->force_pwchange = 0; /* XXX - signal [net] parent to enable forwardings */ } #endif if (check_quietlogin(s, command)) return; display_loginmsg(); do_motd(); } /* * Display the message of the day. */ void do_motd(void) { FILE *f; char buf[256]; if (options.print_motd) { #ifdef HAVE_LOGIN_CAP f = fopen(login_getcapstr(lc, "welcome", "/etc/motd", "/etc/motd"), "r"); #else f = fopen("/etc/motd", "r"); #endif if (f) { while (fgets(buf, sizeof(buf), f)) fputs(buf, stdout); fclose(f); } } } /* * Check for quiet login, either .hushlogin or command given. */ int check_quietlogin(Session *s, const char *command) { char buf[256]; struct passwd *pw = s->pw; struct stat st; /* Return 1 if .hushlogin exists or a command given. */ if (command != NULL) return 1; snprintf(buf, sizeof(buf), "%.200s/.hushlogin", pw->pw_dir); #ifdef HAVE_LOGIN_CAP if (login_getcapbool(lc, "hushlogin", 0) || stat(buf, &st) >= 0) return 1; #else if (stat(buf, &st) >= 0) return 1; #endif return 0; } /* * Sets the value of the given variable in the environment. If the variable * already exists, its value is overridden. */ void child_set_env(char ***envp, u_int *envsizep, const char *name, const char *value) { char **env; u_int envsize; u_int i, namelen; /* * If we're passed an uninitialized list, allocate a single null * entry before continuing. */ if (*envp == NULL && *envsizep == 0) { *envp = xmalloc(sizeof(char *)); *envp[0] = NULL; *envsizep = 1; } /* * Find the slot where the value should be stored. If the variable * already exists, we reuse the slot; otherwise we append a new slot * at the end of the array, expanding if necessary. */ env = *envp; namelen = strlen(name); for (i = 0; env[i]; i++) if (strncmp(env[i], name, namelen) == 0 && env[i][namelen] == '=') break; if (env[i]) { /* Reuse the slot. */ xfree(env[i]); } else { /* New variable. Expand if necessary. */ envsize = *envsizep; if (i >= envsize - 1) { if (envsize >= 1000) fatal("child_set_env: too many env vars"); envsize += 50; env = (*envp) = xrealloc(env, envsize, sizeof(char *)); *envsizep = envsize; } /* Need to set the NULL pointer at end of array beyond the new slot. */ env[i + 1] = NULL; } /* Allocate space and format the variable in the appropriate slot. */ env[i] = xmalloc(strlen(name) + 1 + strlen(value) + 1); snprintf(env[i], strlen(name) + 1 + strlen(value) + 1, "%s=%s", name, value); } /* * Reads environment variables from the given file and adds/overrides them * into the environment. If the file does not exist, this does nothing. * Otherwise, it must consist of empty lines, comments (line starts with '#') * and assignments of the form name=value. No other forms are allowed. */ static void read_environment_file(char ***env, u_int *envsize, const char *filename) { FILE *f; char buf[4096]; char *cp, *value; u_int lineno = 0; f = fopen(filename, "r"); if (!f) return; while (fgets(buf, sizeof(buf), f)) { if (++lineno > 1000) fatal("Too many lines in environment file %s", filename); for (cp = buf; *cp == ' ' || *cp == '\t'; cp++) ; if (!*cp || *cp == '#' || *cp == '\n') continue; cp[strcspn(cp, "\n")] = '\0'; value = strchr(cp, '='); if (value == NULL) { fprintf(stderr, "Bad line %u in %.100s\n", lineno, filename); continue; } /* * Replace the equals sign by nul, and advance value to * the value string. */ *value = '\0'; value++; child_set_env(env, envsize, cp, value); } fclose(f); } #ifdef HAVE_ETC_DEFAULT_LOGIN /* * Return named variable from specified environment, or NULL if not present. */ static char * child_get_env(char **env, const char *name) { int i; size_t len; len = strlen(name); for (i=0; env[i] != NULL; i++) if (strncmp(name, env[i], len) == 0 && env[i][len] == '=') return(env[i] + len + 1); return NULL; } /* * Read /etc/default/login. * We pick up the PATH (or SUPATH for root) and UMASK. */ static void read_etc_default_login(char ***env, u_int *envsize, uid_t uid) { char **tmpenv = NULL, *var; u_int i, tmpenvsize = 0; u_long mask; /* * We don't want to copy the whole file to the child's environment, * so we use a temporary environment and copy the variables we're * interested in. */ read_environment_file(&tmpenv, &tmpenvsize, "/etc/default/login"); if (tmpenv == NULL) return; if (uid == 0) var = child_get_env(tmpenv, "SUPATH"); else var = child_get_env(tmpenv, "PATH"); if (var != NULL) child_set_env(env, envsize, "PATH", var); if ((var = child_get_env(tmpenv, "UMASK")) != NULL) if (sscanf(var, "%5lo", &mask) == 1) umask((mode_t)mask); for (i = 0; tmpenv[i] != NULL; i++) xfree(tmpenv[i]); xfree(tmpenv); } #endif /* HAVE_ETC_DEFAULT_LOGIN */ void copy_environment(char **source, char ***env, u_int *envsize) { char *var_name, *var_val; int i; if (source == NULL) return; for(i = 0; source[i] != NULL; i++) { var_name = xstrdup(source[i]); if ((var_val = strstr(var_name, "=")) == NULL) { xfree(var_name); continue; } *var_val++ = '\0'; debug3("Copy environment: %s=%s", var_name, var_val); child_set_env(env, envsize, var_name, var_val); xfree(var_name); } } static char ** do_setup_env(Session *s, const char *shell) { char buf[256]; u_int i, envsize; char **env, *laddr; struct passwd *pw = s->pw; #if !defined (HAVE_LOGIN_CAP) && !defined (HAVE_CYGWIN) char *path = NULL; #else extern char **environ; char **senv, **var; #endif /* Initialize the environment. */ envsize = 100; env = xcalloc(envsize, sizeof(char *)); env[0] = NULL; #ifdef HAVE_CYGWIN /* * The Windows environment contains some setting which are * important for a running system. They must not be dropped. */ { char **p; p = fetch_windows_environment(); copy_environment(p, &env, &envsize); free_windows_environment(p); } #endif if (getenv("TZ")) child_set_env(&env, &envsize, "TZ", getenv("TZ")); #ifdef GSSAPI /* Allow any GSSAPI methods that we've used to alter * the childs environment as they see fit */ ssh_gssapi_do_child(&env, &envsize); #endif if (!options.use_login) { /* Set basic environment. */ for (i = 0; i < s->num_env; i++) child_set_env(&env, &envsize, s->env[i].name, s->env[i].val); child_set_env(&env, &envsize, "USER", pw->pw_name); child_set_env(&env, &envsize, "LOGNAME", pw->pw_name); #ifdef _AIX child_set_env(&env, &envsize, "LOGIN", pw->pw_name); #endif child_set_env(&env, &envsize, "HOME", pw->pw_dir); snprintf(buf, sizeof buf, "%.200s/%.50s", _PATH_MAILDIR, pw->pw_name); child_set_env(&env, &envsize, "MAIL", buf); #ifdef HAVE_LOGIN_CAP child_set_env(&env, &envsize, "PATH", _PATH_STDPATH); child_set_env(&env, &envsize, "TERM", "su"); senv = environ; environ = xmalloc(sizeof(char *)); *environ = NULL; (void) setusercontext(lc, pw, pw->pw_uid, LOGIN_SETENV|LOGIN_SETPATH); copy_environment(environ, &env, &envsize); for (var = environ; *var != NULL; ++var) xfree(*var); xfree(environ); environ = senv; #else /* HAVE_LOGIN_CAP */ # ifndef HAVE_CYGWIN /* * There's no standard path on Windows. The path contains * important components pointing to the system directories, * needed for loading shared libraries. So the path better * remains intact here. */ # ifdef HAVE_ETC_DEFAULT_LOGIN read_etc_default_login(&env, &envsize, pw->pw_uid); path = child_get_env(env, "PATH"); # endif /* HAVE_ETC_DEFAULT_LOGIN */ if (path == NULL || *path == '\0') { child_set_env(&env, &envsize, "PATH", s->pw->pw_uid == 0 ? SUPERUSER_PATH : _PATH_STDPATH); } # endif /* HAVE_CYGWIN */ #endif /* HAVE_LOGIN_CAP */ /* Normal systems set SHELL by default. */ child_set_env(&env, &envsize, "SHELL", shell); } /* Set custom environment options from RSA authentication. */ if (!options.use_login) { while (custom_environment) { struct envstring *ce = custom_environment; char *str = ce->s; for (i = 0; str[i] != '=' && str[i]; i++) ; if (str[i] == '=') { str[i] = 0; child_set_env(&env, &envsize, str, str + i + 1); } custom_environment = ce->next; xfree(ce->s); xfree(ce); } } /* SSH_CLIENT deprecated */ snprintf(buf, sizeof buf, "%.50s %d %d", get_remote_ipaddr(), get_remote_port(), get_local_port()); child_set_env(&env, &envsize, "SSH_CLIENT", buf); laddr = get_local_ipaddr(packet_get_connection_in()); snprintf(buf, sizeof buf, "%.50s %d %.50s %d", get_remote_ipaddr(), get_remote_port(), laddr, get_local_port()); xfree(laddr); child_set_env(&env, &envsize, "SSH_CONNECTION", buf); if (s->ttyfd != -1) child_set_env(&env, &envsize, "SSH_TTY", s->tty); if (s->term) child_set_env(&env, &envsize, "TERM", s->term); if (s->display) child_set_env(&env, &envsize, "DISPLAY", s->display); if (original_command) child_set_env(&env, &envsize, "SSH_ORIGINAL_COMMAND", original_command); #ifdef _UNICOS if (cray_tmpdir[0] != '\0') child_set_env(&env, &envsize, "TMPDIR", cray_tmpdir); #endif /* _UNICOS */ /* * Since we clear KRB5CCNAME at startup, if it's set now then it * must have been set by a native authentication method (eg AIX or * SIA), so copy it to the child. */ { char *cp; if ((cp = getenv("KRB5CCNAME")) != NULL) child_set_env(&env, &envsize, "KRB5CCNAME", cp); } #ifdef _AIX { char *cp; if ((cp = getenv("AUTHSTATE")) != NULL) child_set_env(&env, &envsize, "AUTHSTATE", cp); read_environment_file(&env, &envsize, "/etc/environment"); } #endif #ifdef KRB5 if (s->authctxt->krb5_ccname) child_set_env(&env, &envsize, "KRB5CCNAME", s->authctxt->krb5_ccname); #endif #ifdef USE_PAM /* * Pull in any environment variables that may have * been set by PAM. */ if (options.use_pam) { char **p; p = fetch_pam_child_environment(); copy_environment(p, &env, &envsize); free_pam_environment(p); p = fetch_pam_environment(); copy_environment(p, &env, &envsize); free_pam_environment(p); } #endif /* USE_PAM */ if (auth_sock_name != NULL) child_set_env(&env, &envsize, SSH_AUTHSOCKET_ENV_NAME, auth_sock_name); /* read $HOME/.ssh/environment. */ if (options.permit_user_env && !options.use_login) { snprintf(buf, sizeof buf, "%.200s/.ssh/environment", strcmp(pw->pw_dir, "/") ? pw->pw_dir : ""); read_environment_file(&env, &envsize, buf); } if (debug_flag) { /* dump the environment */ fprintf(stderr, "Environment:\n"); for (i = 0; env[i]; i++) fprintf(stderr, " %.200s\n", env[i]); } return env; } /* * Run $HOME/.ssh/rc, /etc/ssh/sshrc, or xauth (whichever is found * first in this order). */ static void do_rc_files(Session *s, const char *shell) { FILE *f = NULL; char cmd[1024]; int do_xauth; struct stat st; do_xauth = s->display != NULL && s->auth_proto != NULL && s->auth_data != NULL; /* ignore _PATH_SSH_USER_RC for subsystems and admin forced commands */ if (!s->is_subsystem && options.adm_forced_command == NULL && !no_user_rc && stat(_PATH_SSH_USER_RC, &st) >= 0) { snprintf(cmd, sizeof cmd, "%s -c '%s %s'", shell, _PATH_BSHELL, _PATH_SSH_USER_RC); if (debug_flag) fprintf(stderr, "Running %s\n", cmd); f = popen(cmd, "w"); if (f) { if (do_xauth) fprintf(f, "%s %s\n", s->auth_proto, s->auth_data); pclose(f); } else fprintf(stderr, "Could not run %s\n", _PATH_SSH_USER_RC); } else if (stat(_PATH_SSH_SYSTEM_RC, &st) >= 0) { if (debug_flag) fprintf(stderr, "Running %s %s\n", _PATH_BSHELL, _PATH_SSH_SYSTEM_RC); f = popen(_PATH_BSHELL " " _PATH_SSH_SYSTEM_RC, "w"); if (f) { if (do_xauth) fprintf(f, "%s %s\n", s->auth_proto, s->auth_data); pclose(f); } else fprintf(stderr, "Could not run %s\n", _PATH_SSH_SYSTEM_RC); } else if (do_xauth && options.xauth_location != NULL) { /* Add authority data to .Xauthority if appropriate. */ if (debug_flag) { fprintf(stderr, "Running %.500s remove %.100s\n", options.xauth_location, s->auth_display); fprintf(stderr, "%.500s add %.100s %.100s %.100s\n", options.xauth_location, s->auth_display, s->auth_proto, s->auth_data); } snprintf(cmd, sizeof cmd, "%s -q -", options.xauth_location); f = popen(cmd, "w"); if (f) { fprintf(f, "remove %s\n", s->auth_display); fprintf(f, "add %s %s %s\n", s->auth_display, s->auth_proto, s->auth_data); pclose(f); } else { fprintf(stderr, "Could not run %s\n", cmd); } } } static void do_nologin(struct passwd *pw) { FILE *f = NULL; char buf[1024], *nl, *def_nl = _PATH_NOLOGIN; struct stat sb; #ifdef HAVE_LOGIN_CAP if (login_getcapbool(lc, "ignorenologin", 0) && pw->pw_uid) return; nl = login_getcapstr(lc, "nologin", def_nl, def_nl); #else if (pw->pw_uid == 0) return; nl = def_nl; #endif if (stat(nl, &sb) == -1) { if (nl != def_nl) xfree(nl); return; } /* /etc/nologin exists. Print its contents if we can and exit. */ logit("User %.100s not allowed because %s exists", pw->pw_name, nl); if ((f = fopen(nl, "r")) != NULL) { while (fgets(buf, sizeof(buf), f)) fputs(buf, stderr); fclose(f); } exit(254); } /* * Chroot into a directory after checking it for safety: all path components * must be root-owned directories with strict permissions. */ static void safely_chroot(const char *path, uid_t uid) { const char *cp; char component[MAXPATHLEN]; struct stat st; if (*path != '/') fatal("chroot path does not begin at root"); if (strlen(path) >= sizeof(component)) fatal("chroot path too long"); /* * Descend the path, checking that each component is a * root-owned directory with strict permissions. */ for (cp = path; cp != NULL;) { if ((cp = strchr(cp, '/')) == NULL) strlcpy(component, path, sizeof(component)); else { cp++; memcpy(component, path, cp - path); component[cp - path] = '\0'; } debug3("%s: checking '%s'", __func__, component); if (stat(component, &st) != 0) fatal("%s: stat(\"%s\"): %s", __func__, component, strerror(errno)); if (st.st_uid != 0 || (st.st_mode & 022) != 0) fatal("bad ownership or modes for chroot " "directory %s\"%s\"", cp == NULL ? "" : "component ", component); if (!S_ISDIR(st.st_mode)) fatal("chroot path %s\"%s\" is not a directory", cp == NULL ? "" : "component ", component); } if (chdir(path) == -1) fatal("Unable to chdir to chroot path \"%s\": " "%s", path, strerror(errno)); if (chroot(path) == -1) fatal("chroot(\"%s\"): %s", path, strerror(errno)); if (chdir("/") == -1) fatal("%s: chdir(/) after chroot: %s", __func__, strerror(errno)); verbose("Changed root directory to \"%s\"", path); } /* Set login name, uid, gid, and groups. */ void do_setusercontext(struct passwd *pw) { char *chroot_path, *tmp; platform_setusercontext(pw); if (platform_privileged_uidswap()) { #ifdef HAVE_LOGIN_CAP if (setusercontext(lc, pw, pw->pw_uid, (LOGIN_SETALL & ~(LOGIN_SETENV|LOGIN_SETPATH|LOGIN_SETUSER))) < 0) { perror("unable to set user context"); exit(1); } #else if (setlogin(pw->pw_name) < 0) error("setlogin failed: %s", strerror(errno)); if (setgid(pw->pw_gid) < 0) { perror("setgid"); exit(1); } /* Initialize the group list. */ if (initgroups(pw->pw_name, pw->pw_gid) < 0) { perror("initgroups"); exit(1); } endgrent(); #endif platform_setusercontext_post_groups(pw); if (options.chroot_directory != NULL && strcasecmp(options.chroot_directory, "none") != 0) { tmp = tilde_expand_filename(options.chroot_directory, pw->pw_uid); chroot_path = percent_expand(tmp, "h", pw->pw_dir, "u", pw->pw_name, (char *)NULL); safely_chroot(chroot_path, pw->pw_uid); free(tmp); free(chroot_path); } #ifdef HAVE_LOGIN_CAP if (setusercontext(lc, pw, pw->pw_uid, LOGIN_SETUSER) < 0) { perror("unable to set user context (setuser)"); exit(1); } #else /* Permanently switch to the desired uid. */ permanently_set_uid(pw); #endif } if (getuid() != pw->pw_uid || geteuid() != pw->pw_uid) fatal("Failed to set uids to %u.", (u_int) pw->pw_uid); } static void do_pwchange(Session *s) { fflush(NULL); fprintf(stderr, "WARNING: Your password has expired.\n"); if (s->ttyfd != -1) { fprintf(stderr, "You must change your password now and login again!\n"); #ifdef PASSWD_NEEDS_USERNAME execl(_PATH_PASSWD_PROG, "passwd", s->pw->pw_name, (char *)NULL); #else execl(_PATH_PASSWD_PROG, "passwd", (char *)NULL); #endif perror("passwd"); } else { fprintf(stderr, "Password change required but no TTY available.\n"); } exit(1); } static void launch_login(struct passwd *pw, const char *hostname) { /* Launch login(1). */ execl(LOGIN_PROGRAM, "login", "-h", hostname, #ifdef xxxLOGIN_NEEDS_TERM (s->term ? s->term : "unknown"), #endif /* LOGIN_NEEDS_TERM */ #ifdef LOGIN_NO_ENDOPT "-p", "-f", pw->pw_name, (char *)NULL); #else "-p", "-f", "--", pw->pw_name, (char *)NULL); #endif /* Login couldn't be executed, die. */ perror("login"); exit(1); } static void child_close_fds(void) { if (packet_get_connection_in() == packet_get_connection_out()) close(packet_get_connection_in()); else { close(packet_get_connection_in()); close(packet_get_connection_out()); } /* * Close all descriptors related to channels. They will still remain * open in the parent. */ /* XXX better use close-on-exec? -markus */ channel_close_all(); /* * Close any extra file descriptors. Note that there may still be * descriptors left by system functions. They will be closed later. */ endpwent(); /* * Close any extra open file descriptors so that we don't have them * hanging around in clients. Note that we want to do this after * initgroups, because at least on Solaris 2.3 it leaves file * descriptors open. */ closefrom(STDERR_FILENO + 1); } /* * Performs common processing for the child, such as setting up the * environment, closing extra file descriptors, setting the user and group * ids, and executing the command or shell. */ #define ARGV_MAX 10 void do_child(Session *s, const char *command) { extern char **environ; char **env; char *argv[ARGV_MAX]; const char *shell, *shell0, *hostname = NULL; struct passwd *pw = s->pw; int r = 0; /* remove hostkey from the child's memory */ destroy_sensitive_data(); /* Force a password change */ if (s->authctxt->force_pwchange) { do_setusercontext(pw); child_close_fds(); do_pwchange(s); exit(1); } /* login(1) is only called if we execute the login shell */ if (options.use_login && command != NULL) options.use_login = 0; #ifdef _UNICOS cray_setup(pw->pw_uid, pw->pw_name, command); #endif /* _UNICOS */ /* * Login(1) does this as well, and it needs uid 0 for the "-h" * switch, so we let login(1) to this for us. */ if (!options.use_login) { #ifdef HAVE_OSF_SIA session_setup_sia(pw, s->ttyfd == -1 ? NULL : s->tty); if (!check_quietlogin(s, command)) do_motd(); #else /* HAVE_OSF_SIA */ /* When PAM is enabled we rely on it to do the nologin check */ if (!options.use_pam) do_nologin(pw); do_setusercontext(pw); /* * PAM session modules in do_setusercontext may have * generated messages, so if this in an interactive * login then display them too. */ if (!check_quietlogin(s, command)) display_loginmsg(); #endif /* HAVE_OSF_SIA */ } #ifdef USE_PAM if (options.use_pam && !options.use_login && !is_pam_session_open()) { debug3("PAM session not opened, exiting"); display_loginmsg(); exit(254); } #endif /* * Get the shell from the password data. An empty shell field is * legal, and means /bin/sh. */ shell = (pw->pw_shell[0] == '\0') ? _PATH_BSHELL : pw->pw_shell; /* * Make sure $SHELL points to the shell from the password file, * even if shell is overridden from login.conf */ env = do_setup_env(s, shell); #ifdef HAVE_LOGIN_CAP shell = login_getcapstr(lc, "shell", (char *)shell, (char *)shell); #endif /* we have to stash the hostname before we close our socket. */ if (options.use_login) hostname = get_remote_name_or_ip(utmp_len, options.use_dns); /* * Close the connection descriptors; note that this is the child, and * the server will still have the socket open, and it is important * that we do not shutdown it. Note that the descriptors cannot be * closed before building the environment, as we call * get_remote_ipaddr there. */ child_close_fds(); /* * Must take new environment into use so that .ssh/rc, * /etc/ssh/sshrc and xauth are run in the proper environment. */ environ = env; #if defined(KRB5) && defined(USE_AFS) /* * At this point, we check to see if AFS is active and if we have * a valid Kerberos 5 TGT. If so, it seems like a good idea to see * if we can (and need to) extend the ticket into an AFS token. If * we don't do this, we run into potential problems if the user's * home directory is in AFS and it's not world-readable. */ if (options.kerberos_get_afs_token && k_hasafs() && (s->authctxt->krb5_ctx != NULL)) { char cell[64]; debug("Getting AFS token"); k_setpag(); if (k_afs_cell_of_file(pw->pw_dir, cell, sizeof(cell)) == 0) krb5_afslog(s->authctxt->krb5_ctx, s->authctxt->krb5_fwd_ccache, cell, NULL); krb5_afslog_home(s->authctxt->krb5_ctx, s->authctxt->krb5_fwd_ccache, NULL, NULL, pw->pw_dir); } #endif /* Change current directory to the user's home directory. */ if (chdir(pw->pw_dir) < 0) { /* Suppress missing homedir warning for chroot case */ #ifdef HAVE_LOGIN_CAP r = login_getcapbool(lc, "requirehome", 0); #endif if (r || options.chroot_directory == NULL || strcasecmp(options.chroot_directory, "none") == 0) fprintf(stderr, "Could not chdir to home " "directory %s: %s\n", pw->pw_dir, strerror(errno)); if (r) exit(1); } closefrom(STDERR_FILENO + 1); if (!options.use_login) do_rc_files(s, shell); /* restore SIGPIPE for child */ signal(SIGPIPE, SIG_DFL); if (s->is_subsystem == SUBSYSTEM_INT_SFTP_ERROR) { printf("This service allows sftp connections only.\n"); fflush(NULL); exit(1); } else if (s->is_subsystem == SUBSYSTEM_INT_SFTP) { extern int optind, optreset; int i; char *p, *args; setproctitle("%s@%s", s->pw->pw_name, INTERNAL_SFTP_NAME); args = xstrdup(command ? command : "sftp-server"); for (i = 0, (p = strtok(args, " ")); p; (p = strtok(NULL, " "))) if (i < ARGV_MAX - 1) argv[i++] = p; argv[i] = NULL; optind = optreset = 1; __progname = argv[0]; #ifdef WITH_SELINUX ssh_selinux_change_context("sftpd_t"); #endif exit(sftp_server_main(i, argv, s->pw)); } fflush(NULL); if (options.use_login) { launch_login(pw, hostname); /* NEVERREACHED */ } /* Get the last component of the shell name. */ if ((shell0 = strrchr(shell, '/')) != NULL) shell0++; else shell0 = shell; /* * If we have no command, execute the shell. In this case, the shell * name to be passed in argv[0] is preceded by '-' to indicate that * this is a login shell. */ if (!command) { char argv0[256]; /* Start the shell. Set initial character to '-'. */ argv0[0] = '-'; if (strlcpy(argv0 + 1, shell0, sizeof(argv0) - 1) >= sizeof(argv0) - 1) { errno = EINVAL; perror(shell); exit(1); } /* Execute the shell. */ argv[0] = argv0; argv[1] = NULL; execve(shell, argv, env); /* Executing the shell failed. */ perror(shell); exit(1); } /* * Execute the command using the user's shell. This uses the -c * option to execute the command. */ argv[0] = (char *) shell0; argv[1] = "-c"; argv[2] = (char *) command; argv[3] = NULL; execve(shell, argv, env); perror(shell); exit(1); } void session_unused(int id) { debug3("%s: session id %d unused", __func__, id); if (id >= options.max_sessions || id >= sessions_nalloc) { fatal("%s: insane session id %d (max %d nalloc %d)", __func__, id, options.max_sessions, sessions_nalloc); } bzero(&sessions[id], sizeof(*sessions)); sessions[id].self = id; sessions[id].used = 0; sessions[id].chanid = -1; sessions[id].ptyfd = -1; sessions[id].ttyfd = -1; sessions[id].ptymaster = -1; sessions[id].x11_chanids = NULL; sessions[id].next_unused = sessions_first_unused; sessions_first_unused = id; } Session * session_new(void) { Session *s, *tmp; if (sessions_first_unused == -1) { if (sessions_nalloc >= options.max_sessions) return NULL; debug2("%s: allocate (allocated %d max %d)", __func__, sessions_nalloc, options.max_sessions); tmp = xrealloc(sessions, sessions_nalloc + 1, sizeof(*sessions)); if (tmp == NULL) { error("%s: cannot allocate %d sessions", __func__, sessions_nalloc + 1); return NULL; } sessions = tmp; session_unused(sessions_nalloc++); } if (sessions_first_unused >= sessions_nalloc || sessions_first_unused < 0) { fatal("%s: insane first_unused %d max %d nalloc %d", __func__, sessions_first_unused, options.max_sessions, sessions_nalloc); } s = &sessions[sessions_first_unused]; if (s->used) { fatal("%s: session %d already used", __func__, sessions_first_unused); } sessions_first_unused = s->next_unused; s->used = 1; s->next_unused = -1; debug("session_new: session %d", s->self); return s; } static void session_dump(void) { int i; for (i = 0; i < sessions_nalloc; i++) { Session *s = &sessions[i]; debug("dump: used %d next_unused %d session %d %p " "channel %d pid %ld", s->used, s->next_unused, s->self, s, s->chanid, (long)s->pid); } } int session_open(Authctxt *authctxt, int chanid) { Session *s = session_new(); debug("session_open: channel %d", chanid); if (s == NULL) { error("no more sessions"); return 0; } s->authctxt = authctxt; s->pw = authctxt->pw; if (s->pw == NULL || !authctxt->valid) fatal("no user for session %d", s->self); debug("session_open: session %d: link with channel %d", s->self, chanid); s->chanid = chanid; return 1; } Session * session_by_tty(char *tty) { int i; for (i = 0; i < sessions_nalloc; i++) { Session *s = &sessions[i]; if (s->used && s->ttyfd != -1 && strcmp(s->tty, tty) == 0) { debug("session_by_tty: session %d tty %s", i, tty); return s; } } debug("session_by_tty: unknown tty %.100s", tty); session_dump(); return NULL; } static Session * session_by_channel(int id) { int i; for (i = 0; i < sessions_nalloc; i++) { Session *s = &sessions[i]; if (s->used && s->chanid == id) { debug("session_by_channel: session %d channel %d", i, id); return s; } } debug("session_by_channel: unknown channel %d", id); session_dump(); return NULL; } static Session * session_by_x11_channel(int id) { int i, j; for (i = 0; i < sessions_nalloc; i++) { Session *s = &sessions[i]; if (s->x11_chanids == NULL || !s->used) continue; for (j = 0; s->x11_chanids[j] != -1; j++) { if (s->x11_chanids[j] == id) { debug("session_by_x11_channel: session %d " "channel %d", s->self, id); return s; } } } debug("session_by_x11_channel: unknown channel %d", id); session_dump(); return NULL; } static Session * session_by_pid(pid_t pid) { int i; debug("session_by_pid: pid %ld", (long)pid); for (i = 0; i < sessions_nalloc; i++) { Session *s = &sessions[i]; if (s->used && s->pid == pid) return s; } error("session_by_pid: unknown pid %ld", (long)pid); session_dump(); return NULL; } static int session_window_change_req(Session *s) { s->col = packet_get_int(); s->row = packet_get_int(); s->xpixel = packet_get_int(); s->ypixel = packet_get_int(); packet_check_eom(); pty_change_window_size(s->ptyfd, s->row, s->col, s->xpixel, s->ypixel); return 1; } static int session_pty_req(Session *s) { u_int len; int n_bytes; if (no_pty_flag) { debug("Allocating a pty not permitted for this authentication."); return 0; } if (s->ttyfd != -1) { packet_disconnect("Protocol error: you already have a pty."); return 0; } s->term = packet_get_string(&len); if (compat20) { s->col = packet_get_int(); s->row = packet_get_int(); } else { s->row = packet_get_int(); s->col = packet_get_int(); } s->xpixel = packet_get_int(); s->ypixel = packet_get_int(); if (strcmp(s->term, "") == 0) { xfree(s->term); s->term = NULL; } /* Allocate a pty and open it. */ debug("Allocating pty."); if (!PRIVSEP(pty_allocate(&s->ptyfd, &s->ttyfd, s->tty, sizeof(s->tty)))) { if (s->term) xfree(s->term); s->term = NULL; s->ptyfd = -1; s->ttyfd = -1; error("session_pty_req: session %d alloc failed", s->self); return 0; } debug("session_pty_req: session %d alloc %s", s->self, s->tty); /* for SSH1 the tty modes length is not given */ if (!compat20) n_bytes = packet_remaining(); tty_parse_modes(s->ttyfd, &n_bytes); if (!use_privsep) pty_setowner(s->pw, s->tty); /* Set window size from the packet. */ pty_change_window_size(s->ptyfd, s->row, s->col, s->xpixel, s->ypixel); packet_check_eom(); session_proctitle(s); return 1; } static int session_subsystem_req(Session *s) { struct stat st; u_int len; int success = 0; char *prog, *cmd, *subsys = packet_get_string(&len); u_int i; packet_check_eom(); logit("subsystem request for %.100s by user %s", subsys, s->pw->pw_name); for (i = 0; i < options.num_subsystems; i++) { if (strcmp(subsys, options.subsystem_name[i]) == 0) { prog = options.subsystem_command[i]; cmd = options.subsystem_args[i]; if (strcmp(INTERNAL_SFTP_NAME, prog) == 0) { s->is_subsystem = SUBSYSTEM_INT_SFTP; debug("subsystem: %s", prog); } else { if (stat(prog, &st) < 0) debug("subsystem: cannot stat %s: %s", prog, strerror(errno)); s->is_subsystem = SUBSYSTEM_EXT; debug("subsystem: exec() %s", cmd); } success = do_exec(s, cmd) == 0; break; } } if (!success) logit("subsystem request for %.100s failed, subsystem not found", subsys); xfree(subsys); return success; } static int session_x11_req(Session *s) { int success; if (s->auth_proto != NULL || s->auth_data != NULL) { error("session_x11_req: session %d: " "x11 forwarding already active", s->self); return 0; } s->single_connection = packet_get_char(); s->auth_proto = packet_get_string(NULL); s->auth_data = packet_get_string(NULL); s->screen = packet_get_int(); packet_check_eom(); success = session_setup_x11fwd(s); if (!success) { xfree(s->auth_proto); xfree(s->auth_data); s->auth_proto = NULL; s->auth_data = NULL; } return success; } static int session_shell_req(Session *s) { packet_check_eom(); return do_exec(s, NULL) == 0; } static int session_exec_req(Session *s) { u_int len, success; char *command = packet_get_string(&len); packet_check_eom(); success = do_exec(s, command) == 0; xfree(command); return success; } static int session_break_req(Session *s) { packet_get_int(); /* ignored */ packet_check_eom(); if (s->ttyfd == -1 || tcsendbreak(s->ttyfd, 0) < 0) return 0; return 1; } static int session_env_req(Session *s) { char *name, *val; u_int name_len, val_len, i; name = packet_get_string(&name_len); val = packet_get_string(&val_len); packet_check_eom(); /* Don't set too many environment variables */ if (s->num_env > 128) { debug2("Ignoring env request %s: too many env vars", name); goto fail; } for (i = 0; i < options.num_accept_env; i++) { if (match_pattern(name, options.accept_env[i])) { debug2("Setting env %d: %s=%s", s->num_env, name, val); s->env = xrealloc(s->env, s->num_env + 1, sizeof(*s->env)); s->env[s->num_env].name = name; s->env[s->num_env].val = val; s->num_env++; return (1); } } debug2("Ignoring env request %s: disallowed name", name); fail: xfree(name); xfree(val); return (0); } static int session_auth_agent_req(Session *s) { static int called = 0; packet_check_eom(); if (no_agent_forwarding_flag || !options.allow_agent_forwarding) { debug("session_auth_agent_req: no_agent_forwarding_flag"); return 0; } if (called) { return 0; } else { called = 1; return auth_input_request_forwarding(s->pw); } } int session_input_channel_req(Channel *c, const char *rtype) { int success = 0; Session *s; if ((s = session_by_channel(c->self)) == NULL) { logit("session_input_channel_req: no session %d req %.100s", c->self, rtype); return 0; } debug("session_input_channel_req: session %d req %s", s->self, rtype); /* * a session is in LARVAL state until a shell, a command * or a subsystem is executed */ if (c->type == SSH_CHANNEL_LARVAL) { if (strcmp(rtype, "shell") == 0) { success = session_shell_req(s); } else if (strcmp(rtype, "exec") == 0) { success = session_exec_req(s); } else if (strcmp(rtype, "pty-req") == 0) { success = session_pty_req(s); } else if (strcmp(rtype, "x11-req") == 0) { success = session_x11_req(s); } else if (strcmp(rtype, "auth-agent-req@openssh.com") == 0) { success = session_auth_agent_req(s); } else if (strcmp(rtype, "subsystem") == 0) { success = session_subsystem_req(s); } else if (strcmp(rtype, "env") == 0) { success = session_env_req(s); } } if (strcmp(rtype, "window-change") == 0) { success = session_window_change_req(s); } else if (strcmp(rtype, "break") == 0) { success = session_break_req(s); } return success; } void session_set_fds(Session *s, int fdin, int fdout, int fderr, int ignore_fderr, int is_tty) { if (!compat20) fatal("session_set_fds: called for proto != 2.0"); /* * now that have a child and a pipe to the child, * we can activate our channel and register the fd's */ if (s->chanid == -1) fatal("no channel for session %d", s->self); - channel_set_fds(s->chanid, - fdout, fdin, fderr, - ignore_fderr ? CHAN_EXTENDED_IGNORE : CHAN_EXTENDED_READ, - 1, is_tty, CHAN_SES_WINDOW_DEFAULT); + if (options.hpn_disabled) + channel_set_fds(s->chanid, fdout, fdin, fderr, + ignore_fderr ? CHAN_EXTENDED_IGNORE : CHAN_EXTENDED_READ, + 1, is_tty, CHAN_SES_WINDOW_DEFAULT); + else + channel_set_fds(s->chanid, fdout, fdin, fderr, + ignore_fderr ? CHAN_EXTENDED_IGNORE : CHAN_EXTENDED_READ, + 1, is_tty, options.hpn_buffer_size); } /* * Function to perform pty cleanup. Also called if we get aborted abnormally * (e.g., due to a dropped connection). */ void session_pty_cleanup2(Session *s) { if (s == NULL) { error("session_pty_cleanup: no session"); return; } if (s->ttyfd == -1) return; debug("session_pty_cleanup: session %d release %s", s->self, s->tty); /* Record that the user has logged out. */ if (s->pid != 0) record_logout(s->pid, s->tty, s->pw->pw_name); /* Release the pseudo-tty. */ if (getuid() == 0) pty_release(s->tty); /* * Close the server side of the socket pairs. We must do this after * the pty cleanup, so that another process doesn't get this pty * while we're still cleaning up. */ if (s->ptymaster != -1 && close(s->ptymaster) < 0) error("close(s->ptymaster/%d): %s", s->ptymaster, strerror(errno)); /* unlink pty from session */ s->ttyfd = -1; } void session_pty_cleanup(Session *s) { PRIVSEP(session_pty_cleanup2(s)); } static char * sig2name(int sig) { #define SSH_SIG(x) if (sig == SIG ## x) return #x SSH_SIG(ABRT); SSH_SIG(ALRM); SSH_SIG(FPE); SSH_SIG(HUP); SSH_SIG(ILL); SSH_SIG(INT); SSH_SIG(KILL); SSH_SIG(PIPE); SSH_SIG(QUIT); SSH_SIG(SEGV); SSH_SIG(TERM); SSH_SIG(USR1); SSH_SIG(USR2); #undef SSH_SIG return "SIG@openssh.com"; } static void session_close_x11(int id) { Channel *c; if ((c = channel_by_id(id)) == NULL) { debug("session_close_x11: x11 channel %d missing", id); } else { /* Detach X11 listener */ debug("session_close_x11: detach x11 channel %d", id); channel_cancel_cleanup(id); if (c->ostate != CHAN_OUTPUT_CLOSED) chan_mark_dead(c); } } static void session_close_single_x11(int id, void *arg) { Session *s; u_int i; debug3("session_close_single_x11: channel %d", id); channel_cancel_cleanup(id); if ((s = session_by_x11_channel(id)) == NULL) fatal("session_close_single_x11: no x11 channel %d", id); for (i = 0; s->x11_chanids[i] != -1; i++) { debug("session_close_single_x11: session %d: " "closing channel %d", s->self, s->x11_chanids[i]); /* * The channel "id" is already closing, but make sure we * close all of its siblings. */ if (s->x11_chanids[i] != id) session_close_x11(s->x11_chanids[i]); } xfree(s->x11_chanids); s->x11_chanids = NULL; if (s->display) { xfree(s->display); s->display = NULL; } if (s->auth_proto) { xfree(s->auth_proto); s->auth_proto = NULL; } if (s->auth_data) { xfree(s->auth_data); s->auth_data = NULL; } if (s->auth_display) { xfree(s->auth_display); s->auth_display = NULL; } } static void session_exit_message(Session *s, int status) { Channel *c; if ((c = channel_lookup(s->chanid)) == NULL) fatal("session_exit_message: session %d: no channel %d", s->self, s->chanid); debug("session_exit_message: session %d channel %d pid %ld", s->self, s->chanid, (long)s->pid); if (WIFEXITED(status)) { channel_request_start(s->chanid, "exit-status", 0); packet_put_int(WEXITSTATUS(status)); packet_send(); } else if (WIFSIGNALED(status)) { channel_request_start(s->chanid, "exit-signal", 0); packet_put_cstring(sig2name(WTERMSIG(status))); #ifdef WCOREDUMP packet_put_char(WCOREDUMP(status)? 1 : 0); #else /* WCOREDUMP */ packet_put_char(0); #endif /* WCOREDUMP */ packet_put_cstring(""); packet_put_cstring(""); packet_send(); } else { /* Some weird exit cause. Just exit. */ packet_disconnect("wait returned status %04x.", status); } /* disconnect channel */ debug("session_exit_message: release channel %d", s->chanid); /* * Adjust cleanup callback attachment to send close messages when * the channel gets EOF. The session will be then be closed * by session_close_by_channel when the childs close their fds. */ channel_register_cleanup(c->self, session_close_by_channel, 1); /* * emulate a write failure with 'chan_write_failed', nobody will be * interested in data we write. * Note that we must not call 'chan_read_failed', since there could * be some more data waiting in the pipe. */ if (c->ostate != CHAN_OUTPUT_CLOSED) chan_write_failed(c); } void session_close(Session *s) { u_int i; debug("session_close: session %d pid %ld", s->self, (long)s->pid); if (s->ttyfd != -1) session_pty_cleanup(s); if (s->term) xfree(s->term); if (s->display) xfree(s->display); if (s->x11_chanids) xfree(s->x11_chanids); if (s->auth_display) xfree(s->auth_display); if (s->auth_data) xfree(s->auth_data); if (s->auth_proto) xfree(s->auth_proto); if (s->env != NULL) { for (i = 0; i < s->num_env; i++) { xfree(s->env[i].name); xfree(s->env[i].val); } xfree(s->env); } session_proctitle(s); session_unused(s->self); } void session_close_by_pid(pid_t pid, int status) { Session *s = session_by_pid(pid); if (s == NULL) { debug("session_close_by_pid: no session for pid %ld", (long)pid); return; } if (s->chanid != -1) session_exit_message(s, status); if (s->ttyfd != -1) session_pty_cleanup(s); s->pid = 0; } /* * this is called when a channel dies before * the session 'child' itself dies */ void session_close_by_channel(int id, void *arg) { Session *s = session_by_channel(id); u_int i; if (s == NULL) { debug("session_close_by_channel: no session for id %d", id); return; } debug("session_close_by_channel: channel %d child %ld", id, (long)s->pid); if (s->pid != 0) { debug("session_close_by_channel: channel %d: has child", id); /* * delay detach of session, but release pty, since * the fd's to the child are already closed */ if (s->ttyfd != -1) session_pty_cleanup(s); return; } /* detach by removing callback */ channel_cancel_cleanup(s->chanid); /* Close any X11 listeners associated with this session */ if (s->x11_chanids != NULL) { for (i = 0; s->x11_chanids[i] != -1; i++) { session_close_x11(s->x11_chanids[i]); s->x11_chanids[i] = -1; } } s->chanid = -1; session_close(s); } void session_destroy_all(void (*closefunc)(Session *)) { int i; for (i = 0; i < sessions_nalloc; i++) { Session *s = &sessions[i]; if (s->used) { if (closefunc != NULL) closefunc(s); else session_close(s); } } } static char * session_tty_list(void) { static char buf[1024]; int i; char *cp; buf[0] = '\0'; for (i = 0; i < sessions_nalloc; i++) { Session *s = &sessions[i]; if (s->used && s->ttyfd != -1) { if (strncmp(s->tty, "/dev/", 5) != 0) { cp = strrchr(s->tty, '/'); cp = (cp == NULL) ? s->tty : cp + 1; } else cp = s->tty + 5; if (buf[0] != '\0') strlcat(buf, ",", sizeof buf); strlcat(buf, cp, sizeof buf); } } if (buf[0] == '\0') strlcpy(buf, "notty", sizeof buf); return buf; } void session_proctitle(Session *s) { if (s->pw == NULL) error("no user for session %d", s->self); else setproctitle("%s@%s", s->pw->pw_name, session_tty_list()); } int session_setup_x11fwd(Session *s) { struct stat st; char display[512], auth_display[512]; char hostname[MAXHOSTNAMELEN]; u_int i; if (no_x11_forwarding_flag) { packet_send_debug("X11 forwarding disabled in user configuration file."); return 0; } if (!options.x11_forwarding) { debug("X11 forwarding disabled in server configuration file."); return 0; } if (!options.xauth_location || (stat(options.xauth_location, &st) == -1)) { packet_send_debug("No xauth program; cannot forward with spoofing."); return 0; } if (options.use_login) { packet_send_debug("X11 forwarding disabled; " "not compatible with UseLogin=yes."); return 0; } if (s->display != NULL) { debug("X11 display already set."); return 0; } if (x11_create_display_inet(options.x11_display_offset, options.x11_use_localhost, s->single_connection, &s->display_number, &s->x11_chanids) == -1) { debug("x11_create_display_inet failed."); return 0; } for (i = 0; s->x11_chanids[i] != -1; i++) { channel_register_cleanup(s->x11_chanids[i], session_close_single_x11, 0); } /* Set up a suitable value for the DISPLAY variable. */ if (gethostname(hostname, sizeof(hostname)) < 0) fatal("gethostname: %.100s", strerror(errno)); /* * auth_display must be used as the displayname when the * authorization entry is added with xauth(1). This will be * different than the DISPLAY string for localhost displays. */ if (options.x11_use_localhost) { snprintf(display, sizeof display, "localhost:%u.%u", s->display_number, s->screen); snprintf(auth_display, sizeof auth_display, "unix:%u.%u", s->display_number, s->screen); s->display = xstrdup(display); s->auth_display = xstrdup(auth_display); } else { #ifdef IPADDR_IN_DISPLAY struct hostent *he; struct in_addr my_addr; he = gethostbyname(hostname); if (he == NULL) { error("Can't get IP address for X11 DISPLAY."); packet_send_debug("Can't get IP address for X11 DISPLAY."); return 0; } memcpy(&my_addr, he->h_addr_list[0], sizeof(struct in_addr)); snprintf(display, sizeof display, "%.50s:%u.%u", inet_ntoa(my_addr), s->display_number, s->screen); #else snprintf(display, sizeof display, "%.400s:%u.%u", hostname, s->display_number, s->screen); #endif s->display = xstrdup(display); s->auth_display = xstrdup(display); } return 1; } static void do_authenticated2(Authctxt *authctxt) { server_loop2(authctxt); } void do_cleanup(Authctxt *authctxt) { static int called = 0; debug("do_cleanup"); /* no cleanup if we're in the child for login shell */ if (is_child) return; /* avoid double cleanup */ if (called) return; called = 1; if (authctxt == NULL) return; #ifdef USE_PAM if (options.use_pam) { sshpam_cleanup(); sshpam_thread_cleanup(); } #endif if (!authctxt->authenticated) return; #ifdef KRB5 if (options.kerberos_ticket_cleanup && authctxt->krb5_ctx) krb5_cleanup_proc(authctxt); #endif #ifdef GSSAPI if (compat20 && options.gss_cleanup_creds) ssh_gssapi_cleanup_creds(); #endif /* remove agent socket */ auth_sock_cleanup_proc(authctxt->pw); /* * Cleanup ptys/utmp only if privsep is disabled, * or if running in monitor. */ if (!use_privsep || mm_is_monitor()) session_destroy_all(session_pty_cleanup2); } diff --git a/crypto/openssh/sftp.1 b/crypto/openssh/sftp.1 index 6b506fec8d63..416948291804 100644 --- a/crypto/openssh/sftp.1 +++ b/crypto/openssh/sftp.1 @@ -1,548 +1,549 @@ .\" $OpenBSD: sftp.1,v 1.88 2010/12/04 00:18:01 djm Exp $ .\" $FreeBSD$ .\" .\" Copyright (c) 2001 Damien Miller. All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" 1. Redistributions of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" 2. Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in the .\" documentation and/or other materials provided with the distribution. .\" .\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR .\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES .\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. .\" IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, .\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT .\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, .\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY .\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" .Dd December 4, 2010 .Dt SFTP 1 .Os .Sh NAME .Nm sftp .Nd secure file transfer program .Sh SYNOPSIS .Nm sftp .Bk -words .Op Fl 1246Cpqrv .Op Fl B Ar buffer_size .Op Fl b Ar batchfile .Op Fl c Ar cipher .Op Fl D Ar sftp_server_path .Op Fl F Ar ssh_config .Op Fl i Ar identity_file .Op Fl l Ar limit .Op Fl o Ar ssh_option .Op Fl P Ar port .Op Fl R Ar num_requests .Op Fl S Ar program .Op Fl s Ar subsystem | sftp_server .Ar host .Ek .Nm sftp .Oo Ar user Ns @ Oc Ns .Ar host Ns Op : Ns Ar .Nm sftp .Oo .Ar user Ns @ Oc Ns .Ar host Ns Oo : Ns Ar dir Ns .Op Ar / .Oc .Nm sftp .Fl b Ar batchfile .Oo Ar user Ns @ Oc Ns Ar host .Sh DESCRIPTION .Nm is an interactive file transfer program, similar to .Xr ftp 1 , which performs all operations over an encrypted .Xr ssh 1 transport. It may also use many features of ssh, such as public key authentication and compression. .Nm connects and logs into the specified .Ar host , then enters an interactive command mode. .Pp The second usage format will retrieve files automatically if a non-interactive authentication method is used; otherwise it will do so after successful interactive authentication. .Pp The third usage format allows .Nm to start in a remote directory. .Pp The final usage format allows for automated sessions using the .Fl b option. In such cases, it is necessary to configure non-interactive authentication to obviate the need to enter a password at connection time (see .Xr sshd 8 and .Xr ssh-keygen 1 for details). The options are as follows: .Bl -tag -width Ds .It Fl 1 Specify the use of protocol version 1. .It Fl 2 Specify the use of protocol version 2. .It Fl 4 Forces .Nm to use IPv4 addresses only. .It Fl 6 Forces .Nm to use IPv6 addresses only. .It Fl B Ar buffer_size Specify the size of the buffer that .Nm uses when transferring files. Larger buffers require fewer round trips at the cost of higher memory consumption. The default is 32768 bytes. .It Fl b Ar batchfile Batch mode reads a series of commands from an input .Ar batchfile instead of .Em stdin . Since it lacks user interaction it should be used in conjunction with non-interactive authentication. A .Ar batchfile of .Sq \- may be used to indicate standard input. .Nm will abort if any of the following commands fail: .Ic get , put , rename , ln , .Ic rm , mkdir , chdir , ls , .Ic lchdir , chmod , chown , .Ic chgrp , lpwd , df , symlink , and .Ic lmkdir . Termination on error can be suppressed on a command by command basis by prefixing the command with a .Sq \- character (for example, .Ic -rm /tmp/blah* ) . .It Fl C Enables compression (via ssh's .Fl C flag). .It Fl c Ar cipher Selects the cipher to use for encrypting the data transfers. This option is directly passed to .Xr ssh 1 . .It Fl D Ar sftp_server_path Connect directly to a local sftp server (rather than via .Xr ssh 1 ) . This option may be useful in debugging the client and server. .It Fl F Ar ssh_config Specifies an alternative per-user configuration file for .Xr ssh 1 . This option is directly passed to .Xr ssh 1 . .It Fl i Ar identity_file Selects the file from which the identity (private key) for public key authentication is read. This option is directly passed to .Xr ssh 1 . .It Fl l Ar limit Limits the used bandwidth, specified in Kbit/s. .It Fl o Ar ssh_option Can be used to pass options to .Nm ssh in the format used in .Xr ssh_config 5 . This is useful for specifying options for which there is no separate .Nm sftp command-line flag. For example, to specify an alternate port use: .Ic sftp -oPort=24 . For full details of the options listed below, and their possible values, see .Xr ssh_config 5 . .Pp .Bl -tag -width Ds -offset indent -compact .It AddressFamily .It BatchMode .It BindAddress .It ChallengeResponseAuthentication .It CheckHostIP .It Cipher .It Ciphers .It Compression .It CompressionLevel .It ConnectionAttempts .It ConnectTimeout .It ControlMaster .It ControlPath .It GlobalKnownHostsFile .It GSSAPIAuthentication .It GSSAPIDelegateCredentials .It HashKnownHosts .It Host .It HostbasedAuthentication .It HostKeyAlgorithms .It HostKeyAlias .It HostName .It IdentityFile .It IdentitiesOnly .It IPQoS .It KbdInteractiveDevices .It KexAlgorithms .It LogLevel .It MACs .It NoHostAuthenticationForLocalhost .It NumberOfPasswordPrompts .It PasswordAuthentication .It PKCS11Provider .It Port .It PreferredAuthentications .It Protocol .It ProxyCommand .It PubkeyAuthentication .It RekeyLimit .It RhostsRSAAuthentication .It RSAAuthentication .It SendEnv .It ServerAliveInterval .It ServerAliveCountMax .It StrictHostKeyChecking .It TCPKeepAlive .It UsePrivilegedPort .It User .It UserKnownHostsFile .It VerifyHostKeyDNS .El .It Fl P Ar port Specifies the port to connect to on the remote host. .It Fl p Preserves modification times, access times, and modes from the original files transferred. .It Fl q Quiet mode: disables the progress meter as well as warning and diagnostic messages from .Xr ssh 1 . .It Fl R Ar num_requests Specify how many requests may be outstanding at any one time. Increasing this may slightly improve file transfer speed but will increase memory usage. -The default is 64 outstanding requests. +The default is 256 outstanding requests providing for 8MB +of outstanding data with a 32KB buffer. .It Fl r Recursively copy entire directories when uploading and downloading. Note that .Nm does not follow symbolic links encountered in the tree traversal. .It Fl S Ar program Name of the .Ar program to use for the encrypted connection. The program must understand .Xr ssh 1 options. .It Fl s Ar subsystem | sftp_server Specifies the SSH2 subsystem or the path for an sftp server on the remote host. A path is useful for using .Nm over protocol version 1, or when the remote .Xr sshd 8 does not have an sftp subsystem configured. .It Fl v Raise logging level. This option is also passed to ssh. .El .Sh INTERACTIVE COMMANDS Once in interactive mode, .Nm understands a set of commands similar to those of .Xr ftp 1 . Commands are case insensitive. Pathnames that contain spaces must be enclosed in quotes. Any special characters contained within pathnames that are recognized by .Xr glob 3 must be escaped with backslashes .Pq Sq \e . .Bl -tag -width Ds .It Ic bye Quit .Nm sftp . .It Ic cd Ar path Change remote directory to .Ar path . .It Ic chgrp Ar grp Ar path Change group of file .Ar path to .Ar grp . .Ar path may contain .Xr glob 3 characters and may match multiple files. .Ar grp must be a numeric GID. .It Ic chmod Ar mode Ar path Change permissions of file .Ar path to .Ar mode . .Ar path may contain .Xr glob 3 characters and may match multiple files. .It Ic chown Ar own Ar path Change owner of file .Ar path to .Ar own . .Ar path may contain .Xr glob 3 characters and may match multiple files. .Ar own must be a numeric UID. .It Xo Ic df .Op Fl hi .Op Ar path .Xc Display usage information for the filesystem holding the current directory (or .Ar path if specified). If the .Fl h flag is specified, the capacity information will be displayed using "human-readable" suffixes. The .Fl i flag requests display of inode information in addition to capacity information. This command is only supported on servers that implement the .Dq statvfs@openssh.com extension. .It Ic exit Quit .Nm sftp . .It Xo Ic get .Op Fl Ppr .Ar remote-path .Op Ar local-path .Xc Retrieve the .Ar remote-path and store it on the local machine. If the local path name is not specified, it is given the same name it has on the remote machine. .Ar remote-path may contain .Xr glob 3 characters and may match multiple files. If it does and .Ar local-path is specified, then .Ar local-path must specify a directory. .Pp If either the .Fl P or .Fl p flag is specified, then full file permissions and access times are copied too. .Pp If the .Fl r flag is specified then directories will be copied recursively. Note that .Nm does not follow symbolic links when performing recursive transfers. .It Ic help Display help text. .It Ic lcd Ar path Change local directory to .Ar path . .It Ic lls Op Ar ls-options Op Ar path Display local directory listing of either .Ar path or current directory if .Ar path is not specified. .Ar ls-options may contain any flags supported by the local system's .Xr ls 1 command. .Ar path may contain .Xr glob 3 characters and may match multiple files. .It Ic lmkdir Ar path Create local directory specified by .Ar path . .It Xo Ic ln .Op Fl s .Ar oldpath .Ar newpath .Xc Create a link from .Ar oldpath to .Ar newpath . If the .Fl s flag is specified the created link is a symbolic link, otherwise it is a hard link. .It Ic lpwd Print local working directory. .It Xo Ic ls .Op Fl 1afhlnrSt .Op Ar path .Xc Display a remote directory listing of either .Ar path or the current directory if .Ar path is not specified. .Ar path may contain .Xr glob 3 characters and may match multiple files. .Pp The following flags are recognized and alter the behaviour of .Ic ls accordingly: .Bl -tag -width Ds .It Fl 1 Produce single columnar output. .It Fl a List files beginning with a dot .Pq Sq \&. . .It Fl f Do not sort the listing. The default sort order is lexicographical. .It Fl h When used with a long format option, use unit suffixes: Byte, Kilobyte, Megabyte, Gigabyte, Terabyte, Petabyte, and Exabyte in order to reduce the number of digits to four or fewer using powers of 2 for sizes (K=1024, M=1048576, etc.). .It Fl l Display additional details including permissions and ownership information. .It Fl n Produce a long listing with user and group information presented numerically. .It Fl r Reverse the sort order of the listing. .It Fl S Sort the listing by file size. .It Fl t Sort the listing by last modification time. .El .It Ic lumask Ar umask Set local umask to .Ar umask . .It Ic mkdir Ar path Create remote directory specified by .Ar path . .It Ic progress Toggle display of progress meter. .It Xo Ic put .Op Fl Ppr .Ar local-path .Op Ar remote-path .Xc Upload .Ar local-path and store it on the remote machine. If the remote path name is not specified, it is given the same name it has on the local machine. .Ar local-path may contain .Xr glob 3 characters and may match multiple files. If it does and .Ar remote-path is specified, then .Ar remote-path must specify a directory. .Pp If ether the .Fl P or .Fl p flag is specified, then full file permissions and access times are copied too. .Pp If the .Fl r flag is specified then directories will be copied recursively. Note that .Nm does not follow symbolic links when performing recursive transfers. .It Ic pwd Display remote working directory. .It Ic quit Quit .Nm sftp . .It Ic rename Ar oldpath Ar newpath Rename remote file from .Ar oldpath to .Ar newpath . .It Ic rm Ar path Delete remote file specified by .Ar path . .It Ic rmdir Ar path Remove remote directory specified by .Ar path . .It Ic symlink Ar oldpath Ar newpath Create a symbolic link from .Ar oldpath to .Ar newpath . .It Ic version Display the .Nm protocol version. .It Ic \&! Ns Ar command Execute .Ar command in local shell. .It Ic \&! Escape to local shell. .It Ic \&? Synonym for help. .El .Sh SEE ALSO .Xr ftp 1 , .Xr ls 1 , .Xr scp 1 , .Xr ssh 1 , .Xr ssh-add 1 , .Xr ssh-keygen 1 , .Xr glob 3 , .Xr ssh_config 5 , .Xr sftp-server 8 , .Xr sshd 8 .Rs .%A T. Ylonen .%A S. Lehtinen .%T "SSH File Transfer Protocol" .%N draft-ietf-secsh-filexfer-00.txt .%D January 2001 .%O work in progress material .Re diff --git a/crypto/openssh/sftp.c b/crypto/openssh/sftp.c index ab667f5a55e0..0fb420a8979e 100644 --- a/crypto/openssh/sftp.c +++ b/crypto/openssh/sftp.c @@ -1,2290 +1,2291 @@ /* $OpenBSD: sftp.c,v 1.132 2010/12/04 00:18:01 djm Exp $ */ +/* $FreeBSD$ */ /* * Copyright (c) 2001-2004 Damien Miller * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "includes.h" #include #include #ifdef HAVE_SYS_STAT_H # include #endif #include #include #include #ifdef HAVE_SYS_STATVFS_H #include #endif #include #include #ifdef HAVE_PATHS_H # include #endif #ifdef HAVE_LIBGEN_H #include #endif #ifdef USE_LIBEDIT #include #else typedef void EditLine; #endif #include #include #include #include #include #include #ifdef HAVE_UTIL_H # include #endif #ifdef HAVE_LIBUTIL_H # include #endif #include "xmalloc.h" #include "log.h" #include "pathnames.h" #include "misc.h" #include "sftp.h" #include "buffer.h" #include "sftp-common.h" #include "sftp-client.h" #define DEFAULT_COPY_BUFLEN 32768 /* Size of buffer for up/download */ -#define DEFAULT_NUM_REQUESTS 64 /* # concurrent outstanding requests */ +#define DEFAULT_NUM_REQUESTS 256 /* # concurrent outstanding requests */ /* File to read commands from */ FILE* infile; /* Are we in batchfile mode? */ int batchmode = 0; /* PID of ssh transport process */ static pid_t sshpid = -1; /* This is set to 0 if the progressmeter is not desired. */ int showprogress = 1; /* When this option is set, we always recursively download/upload directories */ int global_rflag = 0; /* When this option is set, the file transfers will always preserve times */ int global_pflag = 0; /* SIGINT received during command processing */ volatile sig_atomic_t interrupted = 0; /* I wish qsort() took a separate ctx for the comparison function...*/ int sort_flag; /* Context used for commandline completion */ struct complete_ctx { struct sftp_conn *conn; char **remote_pathp; }; int remote_glob(struct sftp_conn *, const char *, int, int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */ extern char *__progname; /* Separators for interactive commands */ #define WHITESPACE " \t\r\n" /* ls flags */ #define LS_LONG_VIEW 0x0001 /* Full view ala ls -l */ #define LS_SHORT_VIEW 0x0002 /* Single row view ala ls -1 */ #define LS_NUMERIC_VIEW 0x0004 /* Long view with numeric uid/gid */ #define LS_NAME_SORT 0x0008 /* Sort by name (default) */ #define LS_TIME_SORT 0x0010 /* Sort by mtime */ #define LS_SIZE_SORT 0x0020 /* Sort by file size */ #define LS_REVERSE_SORT 0x0040 /* Reverse sort order */ #define LS_SHOW_ALL 0x0080 /* Don't skip filenames starting with '.' */ #define LS_SI_UNITS 0x0100 /* Display sizes as K, M, G, etc. */ #define VIEW_FLAGS (LS_LONG_VIEW|LS_SHORT_VIEW|LS_NUMERIC_VIEW|LS_SI_UNITS) #define SORT_FLAGS (LS_NAME_SORT|LS_TIME_SORT|LS_SIZE_SORT) /* Commands for interactive mode */ #define I_CHDIR 1 #define I_CHGRP 2 #define I_CHMOD 3 #define I_CHOWN 4 #define I_DF 24 #define I_GET 5 #define I_HELP 6 #define I_LCHDIR 7 #define I_LINK 25 #define I_LLS 8 #define I_LMKDIR 9 #define I_LPWD 10 #define I_LS 11 #define I_LUMASK 12 #define I_MKDIR 13 #define I_PUT 14 #define I_PWD 15 #define I_QUIT 16 #define I_RENAME 17 #define I_RM 18 #define I_RMDIR 19 #define I_SHELL 20 #define I_SYMLINK 21 #define I_VERSION 22 #define I_PROGRESS 23 struct CMD { const char *c; const int n; const int t; }; /* Type of completion */ #define NOARGS 0 #define REMOTE 1 #define LOCAL 2 static const struct CMD cmds[] = { { "bye", I_QUIT, NOARGS }, { "cd", I_CHDIR, REMOTE }, { "chdir", I_CHDIR, REMOTE }, { "chgrp", I_CHGRP, REMOTE }, { "chmod", I_CHMOD, REMOTE }, { "chown", I_CHOWN, REMOTE }, { "df", I_DF, REMOTE }, { "dir", I_LS, REMOTE }, { "exit", I_QUIT, NOARGS }, { "get", I_GET, REMOTE }, { "help", I_HELP, NOARGS }, { "lcd", I_LCHDIR, LOCAL }, { "lchdir", I_LCHDIR, LOCAL }, { "lls", I_LLS, LOCAL }, { "lmkdir", I_LMKDIR, LOCAL }, { "ln", I_LINK, REMOTE }, { "lpwd", I_LPWD, LOCAL }, { "ls", I_LS, REMOTE }, { "lumask", I_LUMASK, NOARGS }, { "mkdir", I_MKDIR, REMOTE }, { "mget", I_GET, REMOTE }, { "mput", I_PUT, LOCAL }, { "progress", I_PROGRESS, NOARGS }, { "put", I_PUT, LOCAL }, { "pwd", I_PWD, REMOTE }, { "quit", I_QUIT, NOARGS }, { "rename", I_RENAME, REMOTE }, { "rm", I_RM, REMOTE }, { "rmdir", I_RMDIR, REMOTE }, { "symlink", I_SYMLINK, REMOTE }, { "version", I_VERSION, NOARGS }, { "!", I_SHELL, NOARGS }, { "?", I_HELP, NOARGS }, { NULL, -1, -1 } }; int interactive_loop(struct sftp_conn *, char *file1, char *file2); /* ARGSUSED */ static void killchild(int signo) { if (sshpid > 1) { kill(sshpid, SIGTERM); waitpid(sshpid, NULL, 0); } _exit(1); } /* ARGSUSED */ static void cmd_interrupt(int signo) { const char msg[] = "\rInterrupt \n"; int olderrno = errno; write(STDERR_FILENO, msg, sizeof(msg) - 1); interrupted = 1; errno = olderrno; } static void help(void) { printf("Available commands:\n" "bye Quit sftp\n" "cd path Change remote directory to 'path'\n" "chgrp grp path Change group of file 'path' to 'grp'\n" "chmod mode path Change permissions of file 'path' to 'mode'\n" "chown own path Change owner of file 'path' to 'own'\n" "df [-hi] [path] Display statistics for current directory or\n" " filesystem containing 'path'\n" "exit Quit sftp\n" "get [-Ppr] remote [local] Download file\n" "help Display this help text\n" "lcd path Change local directory to 'path'\n" "lls [ls-options [path]] Display local directory listing\n" "lmkdir path Create local directory\n" "ln [-s] oldpath newpath Link remote file (-s for symlink)\n" "lpwd Print local working directory\n" "ls [-1afhlnrSt] [path] Display remote directory listing\n" "lumask umask Set local umask to 'umask'\n" "mkdir path Create remote directory\n" "progress Toggle display of progress meter\n" "put [-Ppr] local [remote] Upload file\n" "pwd Display remote working directory\n" "quit Quit sftp\n" "rename oldpath newpath Rename remote file\n" "rm path Delete remote file\n" "rmdir path Remove remote directory\n" "symlink oldpath newpath Symlink remote file\n" "version Show SFTP version\n" "!command Execute 'command' in local shell\n" "! Escape to local shell\n" "? Synonym for help\n"); } static void local_do_shell(const char *args) { int status; char *shell; pid_t pid; if (!*args) args = NULL; if ((shell = getenv("SHELL")) == NULL || *shell == '\0') shell = _PATH_BSHELL; if ((pid = fork()) == -1) fatal("Couldn't fork: %s", strerror(errno)); if (pid == 0) { /* XXX: child has pipe fds to ssh subproc open - issue? */ if (args) { debug3("Executing %s -c \"%s\"", shell, args); execl(shell, shell, "-c", args, (char *)NULL); } else { debug3("Executing %s", shell); execl(shell, shell, (char *)NULL); } fprintf(stderr, "Couldn't execute \"%s\": %s\n", shell, strerror(errno)); _exit(1); } while (waitpid(pid, &status, 0) == -1) if (errno != EINTR) fatal("Couldn't wait for child: %s", strerror(errno)); if (!WIFEXITED(status)) error("Shell exited abnormally"); else if (WEXITSTATUS(status)) error("Shell exited with status %d", WEXITSTATUS(status)); } static void local_do_ls(const char *args) { if (!args || !*args) local_do_shell(_PATH_LS); else { int len = strlen(_PATH_LS " ") + strlen(args) + 1; char *buf = xmalloc(len); /* XXX: quoting - rip quoting code from ftp? */ snprintf(buf, len, _PATH_LS " %s", args); local_do_shell(buf); xfree(buf); } } /* Strip one path (usually the pwd) from the start of another */ static char * path_strip(char *path, char *strip) { size_t len; if (strip == NULL) return (xstrdup(path)); len = strlen(strip); if (strncmp(path, strip, len) == 0) { if (strip[len - 1] != '/' && path[len] == '/') len++; return (xstrdup(path + len)); } return (xstrdup(path)); } static char * make_absolute(char *p, char *pwd) { char *abs_str; /* Derelativise */ if (p && p[0] != '/') { abs_str = path_append(pwd, p); xfree(p); return(abs_str); } else return(p); } static int parse_getput_flags(const char *cmd, char **argv, int argc, int *pflag, int *rflag) { extern int opterr, optind, optopt, optreset; int ch; optind = optreset = 1; opterr = 0; *rflag = *pflag = 0; while ((ch = getopt(argc, argv, "PpRr")) != -1) { switch (ch) { case 'p': case 'P': *pflag = 1; break; case 'r': case 'R': *rflag = 1; break; default: error("%s: Invalid flag -%c", cmd, optopt); return -1; } } return optind; } static int parse_link_flags(const char *cmd, char **argv, int argc, int *sflag) { extern int opterr, optind, optopt, optreset; int ch; optind = optreset = 1; opterr = 0; *sflag = 0; while ((ch = getopt(argc, argv, "s")) != -1) { switch (ch) { case 's': *sflag = 1; break; default: error("%s: Invalid flag -%c", cmd, optopt); return -1; } } return optind; } static int parse_ls_flags(char **argv, int argc, int *lflag) { extern int opterr, optind, optopt, optreset; int ch; optind = optreset = 1; opterr = 0; *lflag = LS_NAME_SORT; while ((ch = getopt(argc, argv, "1Safhlnrt")) != -1) { switch (ch) { case '1': *lflag &= ~VIEW_FLAGS; *lflag |= LS_SHORT_VIEW; break; case 'S': *lflag &= ~SORT_FLAGS; *lflag |= LS_SIZE_SORT; break; case 'a': *lflag |= LS_SHOW_ALL; break; case 'f': *lflag &= ~SORT_FLAGS; break; case 'h': *lflag |= LS_SI_UNITS; break; case 'l': *lflag &= ~LS_SHORT_VIEW; *lflag |= LS_LONG_VIEW; break; case 'n': *lflag &= ~LS_SHORT_VIEW; *lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW; break; case 'r': *lflag |= LS_REVERSE_SORT; break; case 't': *lflag &= ~SORT_FLAGS; *lflag |= LS_TIME_SORT; break; default: error("ls: Invalid flag -%c", optopt); return -1; } } return optind; } static int parse_df_flags(const char *cmd, char **argv, int argc, int *hflag, int *iflag) { extern int opterr, optind, optopt, optreset; int ch; optind = optreset = 1; opterr = 0; *hflag = *iflag = 0; while ((ch = getopt(argc, argv, "hi")) != -1) { switch (ch) { case 'h': *hflag = 1; break; case 'i': *iflag = 1; break; default: error("%s: Invalid flag -%c", cmd, optopt); return -1; } } return optind; } static int is_dir(char *path) { struct stat sb; /* XXX: report errors? */ if (stat(path, &sb) == -1) return(0); return(S_ISDIR(sb.st_mode)); } static int remote_is_dir(struct sftp_conn *conn, char *path) { Attrib *a; /* XXX: report errors? */ if ((a = do_stat(conn, path, 1)) == NULL) return(0); if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) return(0); return(S_ISDIR(a->perm)); } /* Check whether path returned from glob(..., GLOB_MARK, ...) is a directory */ static int pathname_is_dir(char *pathname) { size_t l = strlen(pathname); return l > 0 && pathname[l - 1] == '/'; } static int process_get(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag, int rflag) { char *abs_src = NULL; char *abs_dst = NULL; glob_t g; char *filename, *tmp=NULL; int i, err = 0; abs_src = xstrdup(src); abs_src = make_absolute(abs_src, pwd); memset(&g, 0, sizeof(g)); debug3("Looking up %s", abs_src); if (remote_glob(conn, abs_src, GLOB_MARK, NULL, &g)) { error("File \"%s\" not found.", abs_src); err = -1; goto out; } /* * If multiple matches then dst must be a directory or * unspecified. */ if (g.gl_matchc > 1 && dst != NULL && !is_dir(dst)) { error("Multiple source paths, but destination " "\"%s\" is not a directory", dst); err = -1; goto out; } for (i = 0; g.gl_pathv[i] && !interrupted; i++) { tmp = xstrdup(g.gl_pathv[i]); if ((filename = basename(tmp)) == NULL) { error("basename %s: %s", tmp, strerror(errno)); xfree(tmp); err = -1; goto out; } if (g.gl_matchc == 1 && dst) { if (is_dir(dst)) { abs_dst = path_append(dst, filename); } else { abs_dst = xstrdup(dst); } } else if (dst) { abs_dst = path_append(dst, filename); } else { abs_dst = xstrdup(filename); } xfree(tmp); printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst); if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) { if (download_dir(conn, g.gl_pathv[i], abs_dst, NULL, pflag || global_pflag, 1) == -1) err = -1; } else { if (do_download(conn, g.gl_pathv[i], abs_dst, NULL, pflag || global_pflag) == -1) err = -1; } xfree(abs_dst); abs_dst = NULL; } out: xfree(abs_src); globfree(&g); return(err); } static int process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag, int rflag) { char *tmp_dst = NULL; char *abs_dst = NULL; char *tmp = NULL, *filename = NULL; glob_t g; int err = 0; int i, dst_is_dir = 1; struct stat sb; if (dst) { tmp_dst = xstrdup(dst); tmp_dst = make_absolute(tmp_dst, pwd); } memset(&g, 0, sizeof(g)); debug3("Looking up %s", src); if (glob(src, GLOB_NOCHECK | GLOB_MARK, NULL, &g)) { error("File \"%s\" not found.", src); err = -1; goto out; } /* If we aren't fetching to pwd then stash this status for later */ if (tmp_dst != NULL) dst_is_dir = remote_is_dir(conn, tmp_dst); /* If multiple matches, dst may be directory or unspecified */ if (g.gl_matchc > 1 && tmp_dst && !dst_is_dir) { error("Multiple paths match, but destination " "\"%s\" is not a directory", tmp_dst); err = -1; goto out; } for (i = 0; g.gl_pathv[i] && !interrupted; i++) { if (stat(g.gl_pathv[i], &sb) == -1) { err = -1; error("stat %s: %s", g.gl_pathv[i], strerror(errno)); continue; } tmp = xstrdup(g.gl_pathv[i]); if ((filename = basename(tmp)) == NULL) { error("basename %s: %s", tmp, strerror(errno)); xfree(tmp); err = -1; goto out; } if (g.gl_matchc == 1 && tmp_dst) { /* If directory specified, append filename */ if (dst_is_dir) abs_dst = path_append(tmp_dst, filename); else abs_dst = xstrdup(tmp_dst); } else if (tmp_dst) { abs_dst = path_append(tmp_dst, filename); } else { abs_dst = make_absolute(xstrdup(filename), pwd); } xfree(tmp); printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst); if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) { if (upload_dir(conn, g.gl_pathv[i], abs_dst, pflag || global_pflag, 1) == -1) err = -1; } else { if (do_upload(conn, g.gl_pathv[i], abs_dst, pflag || global_pflag) == -1) err = -1; } } out: if (abs_dst) xfree(abs_dst); if (tmp_dst) xfree(tmp_dst); globfree(&g); return(err); } static int sdirent_comp(const void *aa, const void *bb) { SFTP_DIRENT *a = *(SFTP_DIRENT **)aa; SFTP_DIRENT *b = *(SFTP_DIRENT **)bb; int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1; #define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1)) if (sort_flag & LS_NAME_SORT) return (rmul * strcmp(a->filename, b->filename)); else if (sort_flag & LS_TIME_SORT) return (rmul * NCMP(a->a.mtime, b->a.mtime)); else if (sort_flag & LS_SIZE_SORT) return (rmul * NCMP(a->a.size, b->a.size)); fatal("Unknown ls sort type"); } /* sftp ls.1 replacement for directories */ static int do_ls_dir(struct sftp_conn *conn, char *path, char *strip_path, int lflag) { int n; u_int c = 1, colspace = 0, columns = 1; SFTP_DIRENT **d; if ((n = do_readdir(conn, path, &d)) != 0) return (n); if (!(lflag & LS_SHORT_VIEW)) { u_int m = 0, width = 80; struct winsize ws; char *tmp; /* Count entries for sort and find longest filename */ for (n = 0; d[n] != NULL; n++) { if (d[n]->filename[0] != '.' || (lflag & LS_SHOW_ALL)) m = MAX(m, strlen(d[n]->filename)); } /* Add any subpath that also needs to be counted */ tmp = path_strip(path, strip_path); m += strlen(tmp); xfree(tmp); if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1) width = ws.ws_col; columns = width / (m + 2); columns = MAX(columns, 1); colspace = width / columns; colspace = MIN(colspace, width); } if (lflag & SORT_FLAGS) { for (n = 0; d[n] != NULL; n++) ; /* count entries */ sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT); qsort(d, n, sizeof(*d), sdirent_comp); } for (n = 0; d[n] != NULL && !interrupted; n++) { char *tmp, *fname; if (d[n]->filename[0] == '.' && !(lflag & LS_SHOW_ALL)) continue; tmp = path_append(path, d[n]->filename); fname = path_strip(tmp, strip_path); xfree(tmp); if (lflag & LS_LONG_VIEW) { if (lflag & (LS_NUMERIC_VIEW|LS_SI_UNITS)) { char *lname; struct stat sb; memset(&sb, 0, sizeof(sb)); attrib_to_stat(&d[n]->a, &sb); lname = ls_file(fname, &sb, 1, (lflag & LS_SI_UNITS)); printf("%s\n", lname); xfree(lname); } else printf("%s\n", d[n]->longname); } else { printf("%-*s", colspace, fname); if (c >= columns) { printf("\n"); c = 1; } else c++; } xfree(fname); } if (!(lflag & LS_LONG_VIEW) && (c != 1)) printf("\n"); free_sftp_dirents(d); return (0); } /* sftp ls.1 replacement which handles path globs */ static int do_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path, int lflag) { Attrib *a = NULL; char *fname, *lname; glob_t g; int err; struct winsize ws; u_int i, c = 1, colspace = 0, columns = 1, m = 0, width = 80; memset(&g, 0, sizeof(g)); if (remote_glob(conn, path, GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE|GLOB_KEEPSTAT, NULL, &g) || (g.gl_pathc && !g.gl_matchc)) { if (g.gl_pathc) globfree(&g); error("Can't ls: \"%s\" not found", path); return -1; } if (interrupted) goto out; /* * If the glob returns a single match and it is a directory, * then just list its contents. */ if (g.gl_matchc == 1 && g.gl_statv[0] != NULL && S_ISDIR(g.gl_statv[0]->st_mode)) { err = do_ls_dir(conn, g.gl_pathv[0], strip_path, lflag); globfree(&g); return err; } if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1) width = ws.ws_col; if (!(lflag & LS_SHORT_VIEW)) { /* Count entries for sort and find longest filename */ for (i = 0; g.gl_pathv[i]; i++) m = MAX(m, strlen(g.gl_pathv[i])); columns = width / (m + 2); columns = MAX(columns, 1); colspace = width / columns; } for (i = 0; g.gl_pathv[i] && !interrupted; i++, a = NULL) { fname = path_strip(g.gl_pathv[i], strip_path); if (lflag & LS_LONG_VIEW) { if (g.gl_statv[i] == NULL) { error("no stat information for %s", fname); continue; } lname = ls_file(fname, g.gl_statv[i], 1, (lflag & LS_SI_UNITS)); printf("%s\n", lname); xfree(lname); } else { printf("%-*s", colspace, fname); if (c >= columns) { printf("\n"); c = 1; } else c++; } xfree(fname); } if (!(lflag & LS_LONG_VIEW) && (c != 1)) printf("\n"); out: if (g.gl_pathc) globfree(&g); return 0; } static int do_df(struct sftp_conn *conn, char *path, int hflag, int iflag) { struct sftp_statvfs st; char s_used[FMT_SCALED_STRSIZE]; char s_avail[FMT_SCALED_STRSIZE]; char s_root[FMT_SCALED_STRSIZE]; char s_total[FMT_SCALED_STRSIZE]; unsigned long long ffree; if (do_statvfs(conn, path, &st, 1) == -1) return -1; if (iflag) { ffree = st.f_files ? (100 * (st.f_files - st.f_ffree) / st.f_files) : 0; printf(" Inodes Used Avail " "(root) %%Capacity\n"); printf("%11llu %11llu %11llu %11llu %3llu%%\n", (unsigned long long)st.f_files, (unsigned long long)(st.f_files - st.f_ffree), (unsigned long long)st.f_favail, (unsigned long long)st.f_ffree, ffree); } else if (hflag) { strlcpy(s_used, "error", sizeof(s_used)); strlcpy(s_avail, "error", sizeof(s_avail)); strlcpy(s_root, "error", sizeof(s_root)); strlcpy(s_total, "error", sizeof(s_total)); fmt_scaled((st.f_blocks - st.f_bfree) * st.f_frsize, s_used); fmt_scaled(st.f_bavail * st.f_frsize, s_avail); fmt_scaled(st.f_bfree * st.f_frsize, s_root); fmt_scaled(st.f_blocks * st.f_frsize, s_total); printf(" Size Used Avail (root) %%Capacity\n"); printf("%7sB %7sB %7sB %7sB %3llu%%\n", s_total, s_used, s_avail, s_root, (unsigned long long)(100 * (st.f_blocks - st.f_bfree) / st.f_blocks)); } else { printf(" Size Used Avail " "(root) %%Capacity\n"); printf("%12llu %12llu %12llu %12llu %3llu%%\n", (unsigned long long)(st.f_frsize * st.f_blocks / 1024), (unsigned long long)(st.f_frsize * (st.f_blocks - st.f_bfree) / 1024), (unsigned long long)(st.f_frsize * st.f_bavail / 1024), (unsigned long long)(st.f_frsize * st.f_bfree / 1024), (unsigned long long)(100 * (st.f_blocks - st.f_bfree) / st.f_blocks)); } return 0; } /* * Undo escaping of glob sequences in place. Used to undo extra escaping * applied in makeargv() when the string is destined for a function that * does not glob it. */ static void undo_glob_escape(char *s) { size_t i, j; for (i = j = 0;;) { if (s[i] == '\0') { s[j] = '\0'; return; } if (s[i] != '\\') { s[j++] = s[i++]; continue; } /* s[i] == '\\' */ ++i; switch (s[i]) { case '?': case '[': case '*': case '\\': s[j++] = s[i++]; break; case '\0': s[j++] = '\\'; s[j] = '\0'; return; default: s[j++] = '\\'; s[j++] = s[i++]; break; } } } /* * Split a string into an argument vector using sh(1)-style quoting, * comment and escaping rules, but with some tweaks to handle glob(3) * wildcards. * The "sloppy" flag allows for recovery from missing terminating quote, for * use in parsing incomplete commandlines during tab autocompletion. * * Returns NULL on error or a NULL-terminated array of arguments. * * If "lastquote" is not NULL, the quoting character used for the last * argument is placed in *lastquote ("\0", "'" or "\""). * * If "terminated" is not NULL, *terminated will be set to 1 when the * last argument's quote has been properly terminated or 0 otherwise. * This parameter is only of use if "sloppy" is set. */ #define MAXARGS 128 #define MAXARGLEN 8192 static char ** makeargv(const char *arg, int *argcp, int sloppy, char *lastquote, u_int *terminated) { int argc, quot; size_t i, j; static char argvs[MAXARGLEN]; static char *argv[MAXARGS + 1]; enum { MA_START, MA_SQUOTE, MA_DQUOTE, MA_UNQUOTED } state, q; *argcp = argc = 0; if (strlen(arg) > sizeof(argvs) - 1) { args_too_longs: error("string too long"); return NULL; } if (terminated != NULL) *terminated = 1; if (lastquote != NULL) *lastquote = '\0'; state = MA_START; i = j = 0; for (;;) { if (isspace(arg[i])) { if (state == MA_UNQUOTED) { /* Terminate current argument */ argvs[j++] = '\0'; argc++; state = MA_START; } else if (state != MA_START) argvs[j++] = arg[i]; } else if (arg[i] == '"' || arg[i] == '\'') { q = arg[i] == '"' ? MA_DQUOTE : MA_SQUOTE; if (state == MA_START) { argv[argc] = argvs + j; state = q; if (lastquote != NULL) *lastquote = arg[i]; } else if (state == MA_UNQUOTED) state = q; else if (state == q) state = MA_UNQUOTED; else argvs[j++] = arg[i]; } else if (arg[i] == '\\') { if (state == MA_SQUOTE || state == MA_DQUOTE) { quot = state == MA_SQUOTE ? '\'' : '"'; /* Unescape quote we are in */ /* XXX support \n and friends? */ if (arg[i + 1] == quot) { i++; argvs[j++] = arg[i]; } else if (arg[i + 1] == '?' || arg[i + 1] == '[' || arg[i + 1] == '*') { /* * Special case for sftp: append * double-escaped glob sequence - * glob will undo one level of * escaping. NB. string can grow here. */ if (j >= sizeof(argvs) - 5) goto args_too_longs; argvs[j++] = '\\'; argvs[j++] = arg[i++]; argvs[j++] = '\\'; argvs[j++] = arg[i]; } else { argvs[j++] = arg[i++]; argvs[j++] = arg[i]; } } else { if (state == MA_START) { argv[argc] = argvs + j; state = MA_UNQUOTED; if (lastquote != NULL) *lastquote = '\0'; } if (arg[i + 1] == '?' || arg[i + 1] == '[' || arg[i + 1] == '*' || arg[i + 1] == '\\') { /* * Special case for sftp: append * escaped glob sequence - * glob will undo one level of * escaping. */ argvs[j++] = arg[i++]; argvs[j++] = arg[i]; } else { /* Unescape everything */ /* XXX support \n and friends? */ i++; argvs[j++] = arg[i]; } } } else if (arg[i] == '#') { if (state == MA_SQUOTE || state == MA_DQUOTE) argvs[j++] = arg[i]; else goto string_done; } else if (arg[i] == '\0') { if (state == MA_SQUOTE || state == MA_DQUOTE) { if (sloppy) { state = MA_UNQUOTED; if (terminated != NULL) *terminated = 0; goto string_done; } error("Unterminated quoted argument"); return NULL; } string_done: if (state == MA_UNQUOTED) { argvs[j++] = '\0'; argc++; } break; } else { if (state == MA_START) { argv[argc] = argvs + j; state = MA_UNQUOTED; if (lastquote != NULL) *lastquote = '\0'; } if ((state == MA_SQUOTE || state == MA_DQUOTE) && (arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) { /* * Special case for sftp: escape quoted * glob(3) wildcards. NB. string can grow * here. */ if (j >= sizeof(argvs) - 3) goto args_too_longs; argvs[j++] = '\\'; argvs[j++] = arg[i]; } else argvs[j++] = arg[i]; } i++; } *argcp = argc; return argv; } static int parse_args(const char **cpp, int *pflag, int *rflag, int *lflag, int *iflag, int *hflag, int *sflag, unsigned long *n_arg, char **path1, char **path2) { const char *cmd, *cp = *cpp; char *cp2, **argv; int base = 0; long l; int i, cmdnum, optidx, argc; /* Skip leading whitespace */ cp = cp + strspn(cp, WHITESPACE); /* Check for leading '-' (disable error processing) */ *iflag = 0; if (*cp == '-') { *iflag = 1; cp++; cp = cp + strspn(cp, WHITESPACE); } /* Ignore blank lines and lines which begin with comment '#' char */ if (*cp == '\0' || *cp == '#') return (0); if ((argv = makeargv(cp, &argc, 0, NULL, NULL)) == NULL) return -1; /* Figure out which command we have */ for (i = 0; cmds[i].c != NULL; i++) { if (strcasecmp(cmds[i].c, argv[0]) == 0) break; } cmdnum = cmds[i].n; cmd = cmds[i].c; /* Special case */ if (*cp == '!') { cp++; cmdnum = I_SHELL; } else if (cmdnum == -1) { error("Invalid command."); return -1; } /* Get arguments and parse flags */ *lflag = *pflag = *rflag = *hflag = *n_arg = 0; *path1 = *path2 = NULL; optidx = 1; switch (cmdnum) { case I_GET: case I_PUT: if ((optidx = parse_getput_flags(cmd, argv, argc, pflag, rflag)) == -1) return -1; /* Get first pathname (mandatory) */ if (argc - optidx < 1) { error("You must specify at least one path after a " "%s command.", cmd); return -1; } *path1 = xstrdup(argv[optidx]); /* Get second pathname (optional) */ if (argc - optidx > 1) { *path2 = xstrdup(argv[optidx + 1]); /* Destination is not globbed */ undo_glob_escape(*path2); } break; case I_LINK: if ((optidx = parse_link_flags(cmd, argv, argc, sflag)) == -1) return -1; case I_SYMLINK: case I_RENAME: if (argc - optidx < 2) { error("You must specify two paths after a %s " "command.", cmd); return -1; } *path1 = xstrdup(argv[optidx]); *path2 = xstrdup(argv[optidx + 1]); /* Paths are not globbed */ undo_glob_escape(*path1); undo_glob_escape(*path2); break; case I_RM: case I_MKDIR: case I_RMDIR: case I_CHDIR: case I_LCHDIR: case I_LMKDIR: /* Get pathname (mandatory) */ if (argc - optidx < 1) { error("You must specify a path after a %s command.", cmd); return -1; } *path1 = xstrdup(argv[optidx]); /* Only "rm" globs */ if (cmdnum != I_RM) undo_glob_escape(*path1); break; case I_DF: if ((optidx = parse_df_flags(cmd, argv, argc, hflag, iflag)) == -1) return -1; /* Default to current directory if no path specified */ if (argc - optidx < 1) *path1 = NULL; else { *path1 = xstrdup(argv[optidx]); undo_glob_escape(*path1); } break; case I_LS: if ((optidx = parse_ls_flags(argv, argc, lflag)) == -1) return(-1); /* Path is optional */ if (argc - optidx > 0) *path1 = xstrdup(argv[optidx]); break; case I_LLS: /* Skip ls command and following whitespace */ cp = cp + strlen(cmd) + strspn(cp, WHITESPACE); case I_SHELL: /* Uses the rest of the line */ break; case I_LUMASK: case I_CHMOD: base = 8; case I_CHOWN: case I_CHGRP: /* Get numeric arg (mandatory) */ if (argc - optidx < 1) goto need_num_arg; errno = 0; l = strtol(argv[optidx], &cp2, base); if (cp2 == argv[optidx] || *cp2 != '\0' || ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) || l < 0) { need_num_arg: error("You must supply a numeric argument " "to the %s command.", cmd); return -1; } *n_arg = l; if (cmdnum == I_LUMASK) break; /* Get pathname (mandatory) */ if (argc - optidx < 2) { error("You must specify a path after a %s command.", cmd); return -1; } *path1 = xstrdup(argv[optidx + 1]); break; case I_QUIT: case I_PWD: case I_LPWD: case I_HELP: case I_VERSION: case I_PROGRESS: break; default: fatal("Command not implemented"); } *cpp = cp; return(cmdnum); } static int parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd, int err_abort) { char *path1, *path2, *tmp; int pflag = 0, rflag = 0, lflag = 0, iflag = 0, hflag = 0, sflag = 0; int cmdnum, i; unsigned long n_arg = 0; Attrib a, *aa; char path_buf[MAXPATHLEN]; int err = 0; glob_t g; path1 = path2 = NULL; cmdnum = parse_args(&cmd, &pflag, &rflag, &lflag, &iflag, &hflag, &sflag, &n_arg, &path1, &path2); if (iflag != 0) err_abort = 0; memset(&g, 0, sizeof(g)); /* Perform command */ switch (cmdnum) { case 0: /* Blank line */ break; case -1: /* Unrecognized command */ err = -1; break; case I_GET: err = process_get(conn, path1, path2, *pwd, pflag, rflag); break; case I_PUT: err = process_put(conn, path1, path2, *pwd, pflag, rflag); break; case I_RENAME: path1 = make_absolute(path1, *pwd); path2 = make_absolute(path2, *pwd); err = do_rename(conn, path1, path2); break; case I_SYMLINK: sflag = 1; case I_LINK: path1 = make_absolute(path1, *pwd); path2 = make_absolute(path2, *pwd); err = (sflag ? do_symlink : do_hardlink)(conn, path1, path2); break; case I_RM: path1 = make_absolute(path1, *pwd); remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g); for (i = 0; g.gl_pathv[i] && !interrupted; i++) { printf("Removing %s\n", g.gl_pathv[i]); err = do_rm(conn, g.gl_pathv[i]); if (err != 0 && err_abort) break; } break; case I_MKDIR: path1 = make_absolute(path1, *pwd); attrib_clear(&a); a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS; a.perm = 0777; err = do_mkdir(conn, path1, &a, 1); break; case I_RMDIR: path1 = make_absolute(path1, *pwd); err = do_rmdir(conn, path1); break; case I_CHDIR: path1 = make_absolute(path1, *pwd); if ((tmp = do_realpath(conn, path1)) == NULL) { err = 1; break; } if ((aa = do_stat(conn, tmp, 0)) == NULL) { xfree(tmp); err = 1; break; } if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) { error("Can't change directory: Can't check target"); xfree(tmp); err = 1; break; } if (!S_ISDIR(aa->perm)) { error("Can't change directory: \"%s\" is not " "a directory", tmp); xfree(tmp); err = 1; break; } xfree(*pwd); *pwd = tmp; break; case I_LS: if (!path1) { do_ls_dir(conn, *pwd, *pwd, lflag); break; } /* Strip pwd off beginning of non-absolute paths */ tmp = NULL; if (*path1 != '/') tmp = *pwd; path1 = make_absolute(path1, *pwd); err = do_globbed_ls(conn, path1, tmp, lflag); break; case I_DF: /* Default to current directory if no path specified */ if (path1 == NULL) path1 = xstrdup(*pwd); path1 = make_absolute(path1, *pwd); err = do_df(conn, path1, hflag, iflag); break; case I_LCHDIR: if (chdir(path1) == -1) { error("Couldn't change local directory to " "\"%s\": %s", path1, strerror(errno)); err = 1; } break; case I_LMKDIR: if (mkdir(path1, 0777) == -1) { error("Couldn't create local directory " "\"%s\": %s", path1, strerror(errno)); err = 1; } break; case I_LLS: local_do_ls(cmd); break; case I_SHELL: local_do_shell(cmd); break; case I_LUMASK: umask(n_arg); printf("Local umask: %03lo\n", n_arg); break; case I_CHMOD: path1 = make_absolute(path1, *pwd); attrib_clear(&a); a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS; a.perm = n_arg; remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g); for (i = 0; g.gl_pathv[i] && !interrupted; i++) { printf("Changing mode on %s\n", g.gl_pathv[i]); err = do_setstat(conn, g.gl_pathv[i], &a); if (err != 0 && err_abort) break; } break; case I_CHOWN: case I_CHGRP: path1 = make_absolute(path1, *pwd); remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g); for (i = 0; g.gl_pathv[i] && !interrupted; i++) { if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) { if (err_abort) { err = -1; break; } else continue; } if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) { error("Can't get current ownership of " "remote file \"%s\"", g.gl_pathv[i]); if (err_abort) { err = -1; break; } else continue; } aa->flags &= SSH2_FILEXFER_ATTR_UIDGID; if (cmdnum == I_CHOWN) { printf("Changing owner on %s\n", g.gl_pathv[i]); aa->uid = n_arg; } else { printf("Changing group on %s\n", g.gl_pathv[i]); aa->gid = n_arg; } err = do_setstat(conn, g.gl_pathv[i], aa); if (err != 0 && err_abort) break; } break; case I_PWD: printf("Remote working directory: %s\n", *pwd); break; case I_LPWD: if (!getcwd(path_buf, sizeof(path_buf))) { error("Couldn't get local cwd: %s", strerror(errno)); err = -1; break; } printf("Local working directory: %s\n", path_buf); break; case I_QUIT: /* Processed below */ break; case I_HELP: help(); break; case I_VERSION: printf("SFTP protocol version %u\n", sftp_proto_version(conn)); break; case I_PROGRESS: showprogress = !showprogress; if (showprogress) printf("Progress meter enabled\n"); else printf("Progress meter disabled\n"); break; default: fatal("%d is not implemented", cmdnum); } if (g.gl_pathc) globfree(&g); if (path1) xfree(path1); if (path2) xfree(path2); /* If an unignored error occurs in batch mode we should abort. */ if (err_abort && err != 0) return (-1); else if (cmdnum == I_QUIT) return (1); return (0); } #ifdef USE_LIBEDIT static char * prompt(EditLine *el) { return ("sftp> "); } /* Display entries in 'list' after skipping the first 'len' chars */ static void complete_display(char **list, u_int len) { u_int y, m = 0, width = 80, columns = 1, colspace = 0, llen; struct winsize ws; char *tmp; /* Count entries for sort and find longest */ for (y = 0; list[y]; y++) m = MAX(m, strlen(list[y])); if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1) width = ws.ws_col; m = m > len ? m - len : 0; columns = width / (m + 2); columns = MAX(columns, 1); colspace = width / columns; colspace = MIN(colspace, width); printf("\n"); m = 1; for (y = 0; list[y]; y++) { llen = strlen(list[y]); tmp = llen > len ? list[y] + len : ""; printf("%-*s", colspace, tmp); if (m >= columns) { printf("\n"); m = 1; } else m++; } printf("\n"); } /* * Given a "list" of words that begin with a common prefix of "word", * attempt to find an autocompletion to extends "word" by the next * characters common to all entries in "list". */ static char * complete_ambiguous(const char *word, char **list, size_t count) { if (word == NULL) return NULL; if (count > 0) { u_int y, matchlen = strlen(list[0]); /* Find length of common stem */ for (y = 1; list[y]; y++) { u_int x; for (x = 0; x < matchlen; x++) if (list[0][x] != list[y][x]) break; matchlen = x; } if (matchlen > strlen(word)) { char *tmp = xstrdup(list[0]); tmp[matchlen] = '\0'; return tmp; } } return xstrdup(word); } /* Autocomplete a sftp command */ static int complete_cmd_parse(EditLine *el, char *cmd, int lastarg, char quote, int terminated) { u_int y, count = 0, cmdlen, tmplen; char *tmp, **list, argterm[3]; const LineInfo *lf; list = xcalloc((sizeof(cmds) / sizeof(*cmds)) + 1, sizeof(char *)); /* No command specified: display all available commands */ if (cmd == NULL) { for (y = 0; cmds[y].c; y++) list[count++] = xstrdup(cmds[y].c); list[count] = NULL; complete_display(list, 0); for (y = 0; list[y] != NULL; y++) xfree(list[y]); xfree(list); return count; } /* Prepare subset of commands that start with "cmd" */ cmdlen = strlen(cmd); for (y = 0; cmds[y].c; y++) { if (!strncasecmp(cmd, cmds[y].c, cmdlen)) list[count++] = xstrdup(cmds[y].c); } list[count] = NULL; if (count == 0) return 0; /* Complete ambigious command */ tmp = complete_ambiguous(cmd, list, count); if (count > 1) complete_display(list, 0); for (y = 0; list[y]; y++) xfree(list[y]); xfree(list); if (tmp != NULL) { tmplen = strlen(tmp); cmdlen = strlen(cmd); /* If cmd may be extended then do so */ if (tmplen > cmdlen) if (el_insertstr(el, tmp + cmdlen) == -1) fatal("el_insertstr failed."); lf = el_line(el); /* Terminate argument cleanly */ if (count == 1) { y = 0; if (!terminated) argterm[y++] = quote; if (lastarg || *(lf->cursor) != ' ') argterm[y++] = ' '; argterm[y] = '\0'; if (y > 0 && el_insertstr(el, argterm) == -1) fatal("el_insertstr failed."); } xfree(tmp); } return count; } /* * Determine whether a particular sftp command's arguments (if any) * represent local or remote files. */ static int complete_is_remote(char *cmd) { int i; if (cmd == NULL) return -1; for (i = 0; cmds[i].c; i++) { if (!strncasecmp(cmd, cmds[i].c, strlen(cmds[i].c))) return cmds[i].t; } return -1; } /* Autocomplete a filename "file" */ static int complete_match(EditLine *el, struct sftp_conn *conn, char *remote_path, char *file, int remote, int lastarg, char quote, int terminated) { glob_t g; char *tmp, *tmp2, ins[3]; u_int i, hadglob, pwdlen, len, tmplen, filelen; const LineInfo *lf; /* Glob from "file" location */ if (file == NULL) tmp = xstrdup("*"); else xasprintf(&tmp, "%s*", file); memset(&g, 0, sizeof(g)); if (remote != LOCAL) { tmp = make_absolute(tmp, remote_path); remote_glob(conn, tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g); } else glob(tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g); /* Determine length of pwd so we can trim completion display */ for (hadglob = tmplen = pwdlen = 0; tmp[tmplen] != 0; tmplen++) { /* Terminate counting on first unescaped glob metacharacter */ if (tmp[tmplen] == '*' || tmp[tmplen] == '?') { if (tmp[tmplen] != '*' || tmp[tmplen + 1] != '\0') hadglob = 1; break; } if (tmp[tmplen] == '\\' && tmp[tmplen + 1] != '\0') tmplen++; if (tmp[tmplen] == '/') pwdlen = tmplen + 1; /* track last seen '/' */ } xfree(tmp); if (g.gl_matchc == 0) goto out; if (g.gl_matchc > 1) complete_display(g.gl_pathv, pwdlen); tmp = NULL; /* Don't try to extend globs */ if (file == NULL || hadglob) goto out; tmp2 = complete_ambiguous(file, g.gl_pathv, g.gl_matchc); tmp = path_strip(tmp2, remote_path); xfree(tmp2); if (tmp == NULL) goto out; tmplen = strlen(tmp); filelen = strlen(file); if (tmplen > filelen) { tmp2 = tmp + filelen; len = strlen(tmp2); /* quote argument on way out */ for (i = 0; i < len; i++) { ins[0] = '\\'; ins[1] = tmp2[i]; ins[2] = '\0'; switch (tmp2[i]) { case '\'': case '"': case '\\': case '\t': case '[': case ' ': if (quote == '\0' || tmp2[i] == quote) { if (el_insertstr(el, ins) == -1) fatal("el_insertstr " "failed."); break; } /* FALLTHROUGH */ default: if (el_insertstr(el, ins + 1) == -1) fatal("el_insertstr failed."); break; } } } lf = el_line(el); if (g.gl_matchc == 1) { i = 0; if (!terminated) ins[i++] = quote; if (*(lf->cursor - 1) != '/' && (lastarg || *(lf->cursor) != ' ')) ins[i++] = ' '; ins[i] = '\0'; if (i > 0 && el_insertstr(el, ins) == -1) fatal("el_insertstr failed."); } xfree(tmp); out: globfree(&g); return g.gl_matchc; } /* tab-completion hook function, called via libedit */ static unsigned char complete(EditLine *el, int ch) { char **argv, *line, quote; u_int argc, carg, cursor, len, terminated, ret = CC_ERROR; const LineInfo *lf; struct complete_ctx *complete_ctx; lf = el_line(el); if (el_get(el, EL_CLIENTDATA, (void**)&complete_ctx) != 0) fatal("%s: el_get failed", __func__); /* Figure out which argument the cursor points to */ cursor = lf->cursor - lf->buffer; line = (char *)xmalloc(cursor + 1); memcpy(line, lf->buffer, cursor); line[cursor] = '\0'; argv = makeargv(line, &carg, 1, "e, &terminated); xfree(line); /* Get all the arguments on the line */ len = lf->lastchar - lf->buffer; line = (char *)xmalloc(len + 1); memcpy(line, lf->buffer, len); line[len] = '\0'; argv = makeargv(line, &argc, 1, NULL, NULL); /* Ensure cursor is at EOL or a argument boundary */ if (line[cursor] != ' ' && line[cursor] != '\0' && line[cursor] != '\n') { xfree(line); return ret; } if (carg == 0) { /* Show all available commands */ complete_cmd_parse(el, NULL, argc == carg, '\0', 1); ret = CC_REDISPLAY; } else if (carg == 1 && cursor > 0 && line[cursor - 1] != ' ') { /* Handle the command parsing */ if (complete_cmd_parse(el, argv[0], argc == carg, quote, terminated) != 0) ret = CC_REDISPLAY; } else if (carg >= 1) { /* Handle file parsing */ int remote = complete_is_remote(argv[0]); char *filematch = NULL; if (carg > 1 && line[cursor-1] != ' ') filematch = argv[carg - 1]; if (remote != 0 && complete_match(el, complete_ctx->conn, *complete_ctx->remote_pathp, filematch, remote, carg == argc, quote, terminated) != 0) ret = CC_REDISPLAY; } xfree(line); return ret; } #endif /* USE_LIBEDIT */ int interactive_loop(struct sftp_conn *conn, char *file1, char *file2) { char *remote_path; char *dir = NULL; char cmd[2048]; int err, interactive; EditLine *el = NULL; #ifdef USE_LIBEDIT History *hl = NULL; HistEvent hev; extern char *__progname; struct complete_ctx complete_ctx; if (!batchmode && isatty(STDIN_FILENO)) { if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL) fatal("Couldn't initialise editline"); if ((hl = history_init()) == NULL) fatal("Couldn't initialise editline history"); history(hl, &hev, H_SETSIZE, 100); el_set(el, EL_HIST, history, hl); el_set(el, EL_PROMPT, prompt); el_set(el, EL_EDITOR, "emacs"); el_set(el, EL_TERMINAL, NULL); el_set(el, EL_SIGNAL, 1); el_source(el, NULL); /* Tab Completion */ el_set(el, EL_ADDFN, "ftp-complete", "Context sensitive argument completion", complete); complete_ctx.conn = conn; complete_ctx.remote_pathp = &remote_path; el_set(el, EL_CLIENTDATA, (void*)&complete_ctx); el_set(el, EL_BIND, "^I", "ftp-complete", NULL); } #endif /* USE_LIBEDIT */ remote_path = do_realpath(conn, "."); if (remote_path == NULL) fatal("Need cwd"); if (file1 != NULL) { dir = xstrdup(file1); dir = make_absolute(dir, remote_path); if (remote_is_dir(conn, dir) && file2 == NULL) { printf("Changing to: %s\n", dir); snprintf(cmd, sizeof cmd, "cd \"%s\"", dir); if (parse_dispatch_command(conn, cmd, &remote_path, 1) != 0) { xfree(dir); xfree(remote_path); xfree(conn); return (-1); } } else { if (file2 == NULL) snprintf(cmd, sizeof cmd, "get %s", dir); else snprintf(cmd, sizeof cmd, "get %s %s", dir, file2); err = parse_dispatch_command(conn, cmd, &remote_path, 1); xfree(dir); xfree(remote_path); xfree(conn); return (err); } xfree(dir); } #if defined(HAVE_SETVBUF) && !defined(BROKEN_SETVBUF) setvbuf(stdout, NULL, _IOLBF, 0); setvbuf(infile, NULL, _IOLBF, 0); #else setlinebuf(stdout); setlinebuf(infile); #endif interactive = !batchmode && isatty(STDIN_FILENO); err = 0; for (;;) { char *cp; signal(SIGINT, SIG_IGN); if (el == NULL) { if (interactive) printf("sftp> "); if (fgets(cmd, sizeof(cmd), infile) == NULL) { if (interactive) printf("\n"); break; } if (!interactive) { /* Echo command */ printf("sftp> %s", cmd); if (strlen(cmd) > 0 && cmd[strlen(cmd) - 1] != '\n') printf("\n"); } } else { #ifdef USE_LIBEDIT const char *line; int count = 0; if ((line = el_gets(el, &count)) == NULL || count <= 0) { printf("\n"); break; } history(hl, &hev, H_ENTER, line); if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) { fprintf(stderr, "Error: input line too long\n"); continue; } #endif /* USE_LIBEDIT */ } cp = strrchr(cmd, '\n'); if (cp) *cp = '\0'; /* Handle user interrupts gracefully during commands */ interrupted = 0; signal(SIGINT, cmd_interrupt); err = parse_dispatch_command(conn, cmd, &remote_path, batchmode); if (err != 0) break; } xfree(remote_path); xfree(conn); #ifdef USE_LIBEDIT if (el != NULL) el_end(el); #endif /* USE_LIBEDIT */ /* err == 1 signifies normal "quit" exit */ return (err >= 0 ? 0 : -1); } static void connect_to_server(char *path, char **args, int *in, int *out) { int c_in, c_out; #ifdef USE_PIPES int pin[2], pout[2]; if ((pipe(pin) == -1) || (pipe(pout) == -1)) fatal("pipe: %s", strerror(errno)); *in = pin[0]; *out = pout[1]; c_in = pout[0]; c_out = pin[1]; #else /* USE_PIPES */ int inout[2]; if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1) fatal("socketpair: %s", strerror(errno)); *in = *out = inout[0]; c_in = c_out = inout[1]; #endif /* USE_PIPES */ if ((sshpid = fork()) == -1) fatal("fork: %s", strerror(errno)); else if (sshpid == 0) { if ((dup2(c_in, STDIN_FILENO) == -1) || (dup2(c_out, STDOUT_FILENO) == -1)) { fprintf(stderr, "dup2: %s\n", strerror(errno)); _exit(1); } close(*in); close(*out); close(c_in); close(c_out); /* * The underlying ssh is in the same process group, so we must * ignore SIGINT if we want to gracefully abort commands, * otherwise the signal will make it to the ssh process and * kill it too. Contrawise, since sftp sends SIGTERMs to the * underlying ssh, it must *not* ignore that signal. */ signal(SIGINT, SIG_IGN); signal(SIGTERM, SIG_DFL); execvp(path, args); fprintf(stderr, "exec: %s: %s\n", path, strerror(errno)); _exit(1); } signal(SIGTERM, killchild); signal(SIGINT, killchild); signal(SIGHUP, killchild); close(c_in); close(c_out); } static void usage(void) { extern char *__progname; fprintf(stderr, "usage: %s [-1246Cpqrv] [-B buffer_size] [-b batchfile] [-c cipher]\n" " [-D sftp_server_path] [-F ssh_config] " "[-i identity_file] [-l limit]\n" " [-o ssh_option] [-P port] [-R num_requests] " "[-S program]\n" " [-s subsystem | sftp_server] host\n" " %s [user@]host[:file ...]\n" " %s [user@]host[:dir[/]]\n" " %s -b batchfile [user@]host\n", __progname, __progname, __progname, __progname); exit(1); } int main(int argc, char **argv) { int in, out, ch, err; char *host = NULL, *userhost, *cp, *file2 = NULL; int debug_level = 0, sshver = 2; char *file1 = NULL, *sftp_server = NULL; char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL; const char *errstr; LogLevel ll = SYSLOG_LEVEL_INFO; arglist args; extern int optind; extern char *optarg; struct sftp_conn *conn; size_t copy_buffer_len = DEFAULT_COPY_BUFLEN; size_t num_requests = DEFAULT_NUM_REQUESTS; long long limit_kbps = 0; /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */ sanitise_stdfd(); __progname = ssh_get_progname(argv[0]); memset(&args, '\0', sizeof(args)); args.list = NULL; addargs(&args, "%s", ssh_program); addargs(&args, "-oForwardX11 no"); addargs(&args, "-oForwardAgent no"); addargs(&args, "-oPermitLocalCommand no"); addargs(&args, "-oClearAllForwardings yes"); ll = SYSLOG_LEVEL_INFO; infile = stdin; while ((ch = getopt(argc, argv, "1246hpqrvCc:D:i:l:o:s:S:b:B:F:P:R:")) != -1) { switch (ch) { /* Passed through to ssh(1) */ case '4': case '6': case 'C': addargs(&args, "-%c", ch); break; /* Passed through to ssh(1) with argument */ case 'F': case 'c': case 'i': case 'o': addargs(&args, "-%c", ch); addargs(&args, "%s", optarg); break; case 'q': showprogress = 0; addargs(&args, "-%c", ch); break; case 'P': addargs(&args, "-oPort %s", optarg); break; case 'v': if (debug_level < 3) { addargs(&args, "-v"); ll = SYSLOG_LEVEL_DEBUG1 + debug_level; } debug_level++; break; case '1': sshver = 1; if (sftp_server == NULL) sftp_server = _PATH_SFTP_SERVER; break; case '2': sshver = 2; break; case 'B': copy_buffer_len = strtol(optarg, &cp, 10); if (copy_buffer_len == 0 || *cp != '\0') fatal("Invalid buffer size \"%s\"", optarg); break; case 'b': if (batchmode) fatal("Batch file already specified."); /* Allow "-" as stdin */ if (strcmp(optarg, "-") != 0 && (infile = fopen(optarg, "r")) == NULL) fatal("%s (%s).", strerror(errno), optarg); showprogress = 0; batchmode = 1; addargs(&args, "-obatchmode yes"); break; case 'p': global_pflag = 1; break; case 'D': sftp_direct = optarg; break; case 'l': limit_kbps = strtonum(optarg, 1, 100 * 1024 * 1024, &errstr); if (errstr != NULL) usage(); limit_kbps *= 1024; /* kbps */ break; case 'r': global_rflag = 1; break; case 'R': num_requests = strtol(optarg, &cp, 10); if (num_requests == 0 || *cp != '\0') fatal("Invalid number of requests \"%s\"", optarg); break; case 's': sftp_server = optarg; break; case 'S': ssh_program = optarg; replacearg(&args, 0, "%s", ssh_program); break; case 'h': default: usage(); } } if (!isatty(STDERR_FILENO)) showprogress = 0; log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1); if (sftp_direct == NULL) { if (optind == argc || argc > (optind + 2)) usage(); userhost = xstrdup(argv[optind]); file2 = argv[optind+1]; if ((host = strrchr(userhost, '@')) == NULL) host = userhost; else { *host++ = '\0'; if (!userhost[0]) { fprintf(stderr, "Missing username\n"); usage(); } addargs(&args, "-l"); addargs(&args, "%s", userhost); } if ((cp = colon(host)) != NULL) { *cp++ = '\0'; file1 = cp; } host = cleanhostname(host); if (!*host) { fprintf(stderr, "Missing hostname\n"); usage(); } addargs(&args, "-oProtocol %d", sshver); /* no subsystem if the server-spec contains a '/' */ if (sftp_server == NULL || strchr(sftp_server, '/') == NULL) addargs(&args, "-s"); addargs(&args, "--"); addargs(&args, "%s", host); addargs(&args, "%s", (sftp_server != NULL ? sftp_server : "sftp")); connect_to_server(ssh_program, args.list, &in, &out); } else { args.list = NULL; addargs(&args, "sftp-server"); connect_to_server(sftp_direct, args.list, &in, &out); } freeargs(&args); conn = do_init(in, out, copy_buffer_len, num_requests, limit_kbps); if (conn == NULL) fatal("Couldn't initialise connection to server"); if (!batchmode) { if (sftp_direct == NULL) fprintf(stderr, "Connected to %s.\n", host); else fprintf(stderr, "Attached to %s.\n", sftp_direct); } err = interactive_loop(conn, file1, file2); #if !defined(USE_PIPES) shutdown(in, SHUT_RDWR); shutdown(out, SHUT_RDWR); #endif close(in); close(out); if (batchmode) fclose(infile); while (waitpid(sshpid, NULL, 0) == -1) if (errno != EINTR) fatal("Couldn't wait for ssh process: %s", strerror(errno)); exit(err == 0 ? 0 : 1); } diff --git a/crypto/openssh/ssh.c b/crypto/openssh/ssh.c index c9b29fb6b8a8..1d21f9336154 100644 --- a/crypto/openssh/ssh.c +++ b/crypto/openssh/ssh.c @@ -1,1575 +1,1625 @@ /* $OpenBSD: ssh.c,v 1.356 2011/01/06 22:23:53 djm Exp $ */ +/* $FreeBSD$ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved * Ssh client program. This program can be used to log into a remote machine. * The software supports strong authentication, encryption, and forwarding * of X11, TCP/IP, and authentication connections. * * As far as I am concerned, the code I have written for this software * can be used freely for any purpose. Any derived versions of this * software must be clearly marked as such, and if the derived work is * incompatible with the protocol description in the RFC file, it must be * called by a name other than "ssh" or "Secure Shell". * * Copyright (c) 1999 Niels Provos. All rights reserved. * Copyright (c) 2000, 2001, 2002, 2003 Markus Friedl. All rights reserved. * * Modified to work with SSL by Niels Provos * in Canada (German citizen). * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "includes.h" __RCSID("$FreeBSD$"); #include #ifdef HAVE_SYS_STAT_H # include #endif #include #include #include #include #include #include #include #include #include #ifdef HAVE_PATHS_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include "openbsd-compat/openssl-compat.h" #include "openbsd-compat/sys-queue.h" #include "xmalloc.h" #include "ssh.h" #include "ssh1.h" #include "ssh2.h" #include "canohost.h" #include "compat.h" #include "cipher.h" #include "packet.h" #include "buffer.h" #include "channels.h" #include "key.h" #include "authfd.h" #include "authfile.h" #include "pathnames.h" #include "dispatch.h" #include "clientloop.h" #include "log.h" #include "readconf.h" #include "sshconnect.h" #include "misc.h" #include "kex.h" #include "mac.h" #include "sshpty.h" #include "match.h" #include "msg.h" #include "uidswap.h" #include "roaming.h" #include "version.h" #ifdef ENABLE_PKCS11 #include "ssh-pkcs11.h" #endif extern char *__progname; /* Flag indicating whether debug mode is on. May be set on the command line. */ int debug_flag = 0; /* Flag indicating whether a tty should be allocated */ int tty_flag = 0; int no_tty_flag = 0; int force_tty_flag = 0; /* don't exec a shell */ int no_shell_flag = 0; /* * Flag indicating that nothing should be read from stdin. This can be set * on the command line. */ int stdin_null_flag = 0; /* * Flag indicating that the current process should be backgrounded and * a new slave launched in the foreground for ControlPersist. */ int need_controlpersist_detach = 0; /* Copies of flags for ControlPersist foreground slave */ int ostdin_null_flag, ono_shell_flag, ono_tty_flag, otty_flag; /* * Flag indicating that ssh should fork after authentication. This is useful * so that the passphrase can be entered manually, and then ssh goes to the * background. */ int fork_after_authentication_flag = 0; /* forward stdio to remote host and port */ char *stdio_forward_host = NULL; int stdio_forward_port = 0; /* * General data structure for command line options and options configurable * in configuration files. See readconf.h. */ Options options; /* optional user configfile */ char *config = NULL; /* * Name of the host we are connecting to. This is the name given on the * command line, or the HostName specified for the user-supplied name in a * configuration file. */ char *host; /* socket address the host resolves to */ struct sockaddr_storage hostaddr; /* Private host keys. */ Sensitive sensitive_data; /* Original real UID. */ uid_t original_real_uid; uid_t original_effective_uid; /* command to be executed */ Buffer command; /* Should we execute a command or invoke a subsystem? */ int subsystem_flag = 0; /* # of replies received for global requests */ static int remote_forward_confirms_received = 0; /* mux.c */ extern int muxserver_sock; extern u_int muxclient_command; /* Prints a help message to the user. This function never returns. */ static void usage(void) { fprintf(stderr, "usage: ssh [-1246AaCfgKkMNnqsTtVvXxYy] [-b bind_address] [-c cipher_spec]\n" " [-D [bind_address:]port] [-e escape_char] [-F configfile]\n" " [-I pkcs11] [-i identity_file]\n" " [-L [bind_address:]port:host:hostport]\n" " [-l login_name] [-m mac_spec] [-O ctl_cmd] [-o option] [-p port]\n" " [-R [bind_address:]port:host:hostport] [-S ctl_path]\n" " [-W host:port] [-w local_tun[:remote_tun]]\n" " [user@]hostname [command]\n" ); exit(255); } static int ssh_session(void); static int ssh_session2(void); static void load_public_identity_files(void); static void main_sigchld_handler(int); /* from muxclient.c */ void muxclient(const char *); void muxserver_listen(void); /* * Main program for the ssh client. */ int main(int ac, char **av) { int i, r, opt, exit_status, use_syslog; char *p, *cp, *line, *argv0, buf[MAXPATHLEN], *host_arg; struct stat st; struct passwd *pw; int dummy, timeout_ms; extern int optind, optreset; extern char *optarg; struct servent *sp; Forward fwd; /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */ sanitise_stdfd(); __progname = ssh_get_progname(av[0]); init_rng(); /* * Discard other fds that are hanging around. These can cause problem * with backgrounded ssh processes started by ControlPersist. */ closefrom(STDERR_FILENO + 1); /* * Save the original real uid. It will be needed later (uid-swapping * may clobber the real uid). */ original_real_uid = getuid(); original_effective_uid = geteuid(); /* * Use uid-swapping to give up root privileges for the duration of * option processing. We will re-instantiate the rights when we are * ready to create the privileged port, and will permanently drop * them when the port has been created (actually, when the connection * has been made, as we may need to create the port several times). */ PRIV_END; #ifdef HAVE_SETRLIMIT /* If we are installed setuid root be careful to not drop core. */ if (original_real_uid != original_effective_uid) { struct rlimit rlim; rlim.rlim_cur = rlim.rlim_max = 0; if (setrlimit(RLIMIT_CORE, &rlim) < 0) fatal("setrlimit failed: %.100s", strerror(errno)); } #endif /* Get user data. */ pw = getpwuid(original_real_uid); if (!pw) { logit("You don't exist, go away!"); exit(255); } /* Take a copy of the returned structure. */ pw = pwcopy(pw); /* * Set our umask to something reasonable, as some files are created * with the default umask. This will make them world-readable but * writable only by the owner, which is ok for all files for which we * don't set the modes explicitly. */ umask(022); /* * Initialize option structure to indicate that no values have been * set. */ initialize_options(&options); /* Parse command-line arguments. */ host = NULL; use_syslog = 0; argv0 = av[0]; again: while ((opt = getopt(ac, av, "1246ab:c:e:fgi:kl:m:no:p:qstvx" "ACD:F:I:KL:MNO:PR:S:TVw:W:XYy")) != -1) { switch (opt) { case '1': options.protocol = SSH_PROTO_1; break; case '2': options.protocol = SSH_PROTO_2; break; case '4': options.address_family = AF_INET; break; case '6': options.address_family = AF_INET6; break; case 'n': stdin_null_flag = 1; break; case 'f': fork_after_authentication_flag = 1; stdin_null_flag = 1; break; case 'x': options.forward_x11 = 0; break; case 'X': options.forward_x11 = 1; break; case 'y': use_syslog = 1; break; case 'Y': options.forward_x11 = 1; options.forward_x11_trusted = 1; break; case 'g': options.gateway_ports = 1; break; case 'O': if (stdio_forward_host != NULL) fatal("Cannot specify multiplexing " "command with -W"); else if (muxclient_command != 0) fatal("Multiplexing command already specified"); if (strcmp(optarg, "check") == 0) muxclient_command = SSHMUX_COMMAND_ALIVE_CHECK; else if (strcmp(optarg, "forward") == 0) muxclient_command = SSHMUX_COMMAND_FORWARD; else if (strcmp(optarg, "exit") == 0) muxclient_command = SSHMUX_COMMAND_TERMINATE; else fatal("Invalid multiplex command."); break; case 'P': /* deprecated */ options.use_privileged_port = 0; break; case 'a': options.forward_agent = 0; break; case 'A': options.forward_agent = 1; break; case 'k': options.gss_deleg_creds = 0; break; case 'K': options.gss_authentication = 1; options.gss_deleg_creds = 1; break; case 'i': if (stat(optarg, &st) < 0) { fprintf(stderr, "Warning: Identity file %s " "not accessible: %s.\n", optarg, strerror(errno)); break; } if (options.num_identity_files >= SSH_MAX_IDENTITY_FILES) fatal("Too many identity files specified " "(max %d)", SSH_MAX_IDENTITY_FILES); options.identity_files[options.num_identity_files++] = xstrdup(optarg); break; case 'I': #ifdef ENABLE_PKCS11 options.pkcs11_provider = xstrdup(optarg); #else fprintf(stderr, "no support for PKCS#11.\n"); #endif break; case 't': if (tty_flag) force_tty_flag = 1; tty_flag = 1; break; case 'v': if (debug_flag == 0) { debug_flag = 1; options.log_level = SYSLOG_LEVEL_DEBUG1; } else { if (options.log_level < SYSLOG_LEVEL_DEBUG3) options.log_level++; break; } /* FALLTHROUGH */ case 'V': fprintf(stderr, "%s, %s\n", SSH_RELEASE, SSLeay_version(SSLEAY_VERSION)); if (opt == 'V') exit(0); break; case 'w': if (options.tun_open == -1) options.tun_open = SSH_TUNMODE_DEFAULT; options.tun_local = a2tun(optarg, &options.tun_remote); if (options.tun_local == SSH_TUNID_ERR) { fprintf(stderr, "Bad tun device '%s'\n", optarg); exit(255); } break; case 'W': if (stdio_forward_host != NULL) fatal("stdio forward already specified"); if (muxclient_command != 0) fatal("Cannot specify stdio forward with -O"); if (parse_forward(&fwd, optarg, 1, 0)) { stdio_forward_host = fwd.listen_host; stdio_forward_port = fwd.listen_port; xfree(fwd.connect_host); } else { fprintf(stderr, "Bad stdio forwarding specification '%s'\n", optarg); exit(255); } no_tty_flag = 1; no_shell_flag = 1; options.clear_forwardings = 1; options.exit_on_forward_failure = 1; break; case 'q': options.log_level = SYSLOG_LEVEL_QUIET; break; case 'e': if (optarg[0] == '^' && optarg[2] == 0 && (u_char) optarg[1] >= 64 && (u_char) optarg[1] < 128) options.escape_char = (u_char) optarg[1] & 31; else if (strlen(optarg) == 1) options.escape_char = (u_char) optarg[0]; else if (strcmp(optarg, "none") == 0) options.escape_char = SSH_ESCAPECHAR_NONE; else { fprintf(stderr, "Bad escape character '%s'.\n", optarg); exit(255); } break; case 'c': if (ciphers_valid(optarg)) { /* SSH2 only */ options.ciphers = xstrdup(optarg); options.cipher = SSH_CIPHER_INVALID; } else { /* SSH1 only */ options.cipher = cipher_number(optarg); if (options.cipher == -1) { fprintf(stderr, "Unknown cipher type '%s'\n", optarg); exit(255); } if (options.cipher == SSH_CIPHER_3DES) options.ciphers = "3des-cbc"; else if (options.cipher == SSH_CIPHER_BLOWFISH) options.ciphers = "blowfish-cbc"; else options.ciphers = (char *)-1; } break; case 'm': if (mac_valid(optarg)) options.macs = xstrdup(optarg); else { fprintf(stderr, "Unknown mac type '%s'\n", optarg); exit(255); } break; case 'M': if (options.control_master == SSHCTL_MASTER_YES) options.control_master = SSHCTL_MASTER_ASK; else options.control_master = SSHCTL_MASTER_YES; break; case 'p': options.port = a2port(optarg); if (options.port <= 0) { fprintf(stderr, "Bad port '%s'\n", optarg); exit(255); } break; case 'l': options.user = optarg; break; case 'L': if (parse_forward(&fwd, optarg, 0, 0)) add_local_forward(&options, &fwd); else { fprintf(stderr, "Bad local forwarding specification '%s'\n", optarg); exit(255); } break; case 'R': if (parse_forward(&fwd, optarg, 0, 1)) { add_remote_forward(&options, &fwd); } else { fprintf(stderr, "Bad remote forwarding specification " "'%s'\n", optarg); exit(255); } break; case 'D': if (parse_forward(&fwd, optarg, 1, 0)) { add_local_forward(&options, &fwd); } else { fprintf(stderr, "Bad dynamic forwarding specification " "'%s'\n", optarg); exit(255); } break; case 'C': options.compression = 1; break; case 'N': no_shell_flag = 1; no_tty_flag = 1; break; case 'T': no_tty_flag = 1; +#ifdef NONE_CIPHER_ENABLED + /* + * Ensure that the user does not try to backdoor a + * NONE cipher switch on an interactive session by + * explicitly disabling it if the user asks for a + * session without a tty. + */ + options.none_switch = 0; +#endif break; case 'o': dummy = 1; line = xstrdup(optarg); if (process_config_line(&options, host ? host : "", line, "command-line", 0, &dummy) != 0) exit(255); xfree(line); break; case 's': subsystem_flag = 1; break; case 'S': if (options.control_path != NULL) free(options.control_path); options.control_path = xstrdup(optarg); break; case 'b': options.bind_address = optarg; break; case 'F': config = optarg; break; default: usage(); } } ac -= optind; av += optind; if (ac > 0 && !host) { if (strrchr(*av, '@')) { p = xstrdup(*av); cp = strrchr(p, '@'); if (cp == NULL || cp == p) usage(); options.user = p; *cp = '\0'; host = ++cp; } else host = *av; if (ac > 1) { optind = optreset = 1; goto again; } ac--, av++; } /* Check that we got a host name. */ if (!host) usage(); OpenSSL_add_all_algorithms(); ERR_load_crypto_strings(); /* Initialize the command to execute on remote host. */ buffer_init(&command); /* * Save the command to execute on the remote host in a buffer. There * is no limit on the length of the command, except by the maximum * packet size. Also sets the tty flag if there is no command. */ if (!ac) { /* No command specified - execute shell on a tty. */ tty_flag = 1; if (subsystem_flag) { fprintf(stderr, "You must specify a subsystem to invoke.\n"); usage(); } } else { /* A command has been specified. Store it into the buffer. */ for (i = 0; i < ac; i++) { if (i) buffer_append(&command, " ", 1); buffer_append(&command, av[i], strlen(av[i])); } } /* Cannot fork to background if no command. */ if (fork_after_authentication_flag && buffer_len(&command) == 0 && !no_shell_flag) fatal("Cannot fork into background without a command " "to execute."); /* Allocate a tty by default if no command specified. */ if (buffer_len(&command) == 0) tty_flag = 1; /* Force no tty */ if (no_tty_flag || muxclient_command != 0) tty_flag = 0; /* Do not allocate a tty if stdin is not a tty. */ if ((!isatty(fileno(stdin)) || stdin_null_flag) && !force_tty_flag) { if (tty_flag) logit("Pseudo-terminal will not be allocated because " "stdin is not a terminal."); tty_flag = 0; } /* * Initialize "log" output. Since we are the client all output * actually goes to stderr. */ log_init(argv0, options.log_level == -1 ? SYSLOG_LEVEL_INFO : options.log_level, SYSLOG_FACILITY_USER, !use_syslog); /* * Read per-user configuration file. Ignore the system wide config * file if the user specifies a config file on the command line. */ if (config != NULL) { if (!read_config_file(config, host, &options, 0)) fatal("Can't open user config file %.100s: " "%.100s", config, strerror(errno)); } else { r = snprintf(buf, sizeof buf, "%s/%s", pw->pw_dir, _PATH_SSH_USER_CONFFILE); if (r > 0 && (size_t)r < sizeof(buf)) (void)read_config_file(buf, host, &options, 1); /* Read systemwide configuration file after use config. */ (void)read_config_file(_PATH_HOST_CONFIG_FILE, host, &options, 0); } /* Fill configuration defaults. */ fill_default_options(&options); channel_set_af(options.address_family); /* reinit */ log_init(argv0, options.log_level, SYSLOG_FACILITY_USER, !use_syslog); seed_rng(); if (options.user == NULL) options.user = xstrdup(pw->pw_name); /* Get default port if port has not been set. */ if (options.port == 0) { sp = getservbyname(SSH_SERVICE_NAME, "tcp"); options.port = sp ? ntohs(sp->s_port) : SSH_DEFAULT_PORT; } /* preserve host name given on command line for %n expansion */ host_arg = host; if (options.hostname != NULL) { host = percent_expand(options.hostname, "h", host, (char *)NULL); } if (options.local_command != NULL) { char thishost[NI_MAXHOST]; if (gethostname(thishost, sizeof(thishost)) == -1) fatal("gethostname: %s", strerror(errno)); snprintf(buf, sizeof(buf), "%d", options.port); debug3("expanding LocalCommand: %s", options.local_command); cp = options.local_command; options.local_command = percent_expand(cp, "d", pw->pw_dir, "h", host, "l", thishost, "n", host_arg, "r", options.user, "p", buf, "u", pw->pw_name, (char *)NULL); debug3("expanded LocalCommand: %s", options.local_command); xfree(cp); } /* Find canonic host name. */ if (strchr(host, '.') == 0) { struct addrinfo hints; struct addrinfo *ai = NULL; int errgai; memset(&hints, 0, sizeof(hints)); hints.ai_family = options.address_family; hints.ai_flags = AI_CANONNAME; hints.ai_socktype = SOCK_STREAM; errgai = getaddrinfo(host, NULL, &hints, &ai); if (errgai == 0) { if (ai->ai_canonname != NULL) host = xstrdup(ai->ai_canonname); freeaddrinfo(ai); } } /* force lowercase for hostkey matching */ if (options.host_key_alias != NULL) { for (p = options.host_key_alias; *p; p++) if (isupper(*p)) *p = (char)tolower(*p); } if (options.proxy_command != NULL && strcmp(options.proxy_command, "none") == 0) { xfree(options.proxy_command); options.proxy_command = NULL; } if (options.control_path != NULL && strcmp(options.control_path, "none") == 0) { xfree(options.control_path); options.control_path = NULL; } if (options.control_path != NULL) { char thishost[NI_MAXHOST]; if (gethostname(thishost, sizeof(thishost)) == -1) fatal("gethostname: %s", strerror(errno)); snprintf(buf, sizeof(buf), "%d", options.port); cp = tilde_expand_filename(options.control_path, original_real_uid); xfree(options.control_path); options.control_path = percent_expand(cp, "p", buf, "h", host, "r", options.user, "l", thishost, (char *)NULL); xfree(cp); } if (muxclient_command != 0 && options.control_path == NULL) fatal("No ControlPath specified for \"-O\" command"); if (options.control_path != NULL) muxclient(options.control_path); timeout_ms = options.connection_timeout * 1000; /* Open a connection to the remote host. */ if (ssh_connect(host, &hostaddr, options.port, options.address_family, options.connection_attempts, &timeout_ms, options.tcp_keep_alive, #ifdef HAVE_CYGWIN options.use_privileged_port, #else original_effective_uid == 0 && options.use_privileged_port, #endif options.proxy_command) != 0) exit(255); if (timeout_ms > 0) debug3("timeout: %d ms remain after connect", timeout_ms); /* * If we successfully made the connection, load the host private key * in case we will need it later for combined rsa-rhosts * authentication. This must be done before releasing extra * privileges, because the file is only readable by root. * If we cannot access the private keys, load the public keys * instead and try to execute the ssh-keysign helper instead. */ sensitive_data.nkeys = 0; sensitive_data.keys = NULL; sensitive_data.external_keysign = 0; if (options.rhosts_rsa_authentication || options.hostbased_authentication) { sensitive_data.nkeys = 7; sensitive_data.keys = xcalloc(sensitive_data.nkeys, sizeof(Key)); for (i = 0; i < sensitive_data.nkeys; i++) sensitive_data.keys[i] = NULL; PRIV_START; sensitive_data.keys[0] = key_load_private_type(KEY_RSA1, _PATH_HOST_KEY_FILE, "", NULL, NULL); sensitive_data.keys[1] = key_load_private_cert(KEY_DSA, _PATH_HOST_DSA_KEY_FILE, "", NULL); #ifdef OPENSSL_HAS_ECC sensitive_data.keys[2] = key_load_private_cert(KEY_ECDSA, _PATH_HOST_ECDSA_KEY_FILE, "", NULL); #endif sensitive_data.keys[3] = key_load_private_cert(KEY_RSA, _PATH_HOST_RSA_KEY_FILE, "", NULL); sensitive_data.keys[4] = key_load_private_type(KEY_DSA, _PATH_HOST_DSA_KEY_FILE, "", NULL, NULL); #ifdef OPENSSL_HAS_ECC sensitive_data.keys[5] = key_load_private_type(KEY_ECDSA, _PATH_HOST_ECDSA_KEY_FILE, "", NULL, NULL); #endif sensitive_data.keys[6] = key_load_private_type(KEY_RSA, _PATH_HOST_RSA_KEY_FILE, "", NULL, NULL); PRIV_END; if (options.hostbased_authentication == 1 && sensitive_data.keys[0] == NULL && sensitive_data.keys[4] == NULL && sensitive_data.keys[5] == NULL && sensitive_data.keys[6] == NULL) { sensitive_data.keys[1] = key_load_cert( _PATH_HOST_DSA_KEY_FILE); #ifdef OPENSSL_HAS_ECC sensitive_data.keys[2] = key_load_cert( _PATH_HOST_ECDSA_KEY_FILE); #endif sensitive_data.keys[3] = key_load_cert( _PATH_HOST_RSA_KEY_FILE); sensitive_data.keys[4] = key_load_public( _PATH_HOST_DSA_KEY_FILE, NULL); #ifdef OPENSSL_HAS_ECC sensitive_data.keys[5] = key_load_public( _PATH_HOST_ECDSA_KEY_FILE, NULL); #endif sensitive_data.keys[6] = key_load_public( _PATH_HOST_RSA_KEY_FILE, NULL); sensitive_data.external_keysign = 1; } } /* * Get rid of any extra privileges that we may have. We will no * longer need them. Also, extra privileges could make it very hard * to read identity files and other non-world-readable files from the * user's home directory if it happens to be on a NFS volume where * root is mapped to nobody. */ if (original_effective_uid == 0) { PRIV_START; permanently_set_uid(pw); } /* * Now that we are back to our own permissions, create ~/.ssh * directory if it doesn't already exist. */ r = snprintf(buf, sizeof buf, "%s%s%s", pw->pw_dir, strcmp(pw->pw_dir, "/") ? "/" : "", _PATH_SSH_USER_DIR); if (r > 0 && (size_t)r < sizeof(buf) && stat(buf, &st) < 0) { #ifdef WITH_SELINUX ssh_selinux_setfscreatecon(buf); #endif if (mkdir(buf, 0700) < 0) error("Could not create directory '%.200s'.", buf); #ifdef WITH_SELINUX ssh_selinux_setfscreatecon(NULL); #endif } /* load options.identity_files */ load_public_identity_files(); /* Expand ~ in known host file names. */ /* XXX mem-leaks: */ options.system_hostfile = tilde_expand_filename(options.system_hostfile, original_real_uid); options.user_hostfile = tilde_expand_filename(options.user_hostfile, original_real_uid); options.system_hostfile2 = tilde_expand_filename(options.system_hostfile2, original_real_uid); options.user_hostfile2 = tilde_expand_filename(options.user_hostfile2, original_real_uid); signal(SIGPIPE, SIG_IGN); /* ignore SIGPIPE early */ signal(SIGCHLD, main_sigchld_handler); /* Log into the remote system. Never returns if the login fails. */ ssh_login(&sensitive_data, host, (struct sockaddr *)&hostaddr, options.port, pw, timeout_ms); if (packet_connection_is_on_socket()) { verbose("Authenticated to %s ([%s]:%d).", host, get_remote_ipaddr(), get_remote_port()); } else { verbose("Authenticated to %s (via proxy).", host); } /* We no longer need the private host keys. Clear them now. */ if (sensitive_data.nkeys != 0) { for (i = 0; i < sensitive_data.nkeys; i++) { if (sensitive_data.keys[i] != NULL) { /* Destroys contents safely */ debug3("clear hostkey %d", i); key_free(sensitive_data.keys[i]); sensitive_data.keys[i] = NULL; } } xfree(sensitive_data.keys); } for (i = 0; i < options.num_identity_files; i++) { if (options.identity_files[i]) { xfree(options.identity_files[i]); options.identity_files[i] = NULL; } if (options.identity_keys[i]) { key_free(options.identity_keys[i]); options.identity_keys[i] = NULL; } } exit_status = compat20 ? ssh_session2() : ssh_session(); packet_close(); if (options.control_path != NULL && muxserver_sock != -1) unlink(options.control_path); /* Kill ProxyCommand if it is running. */ ssh_kill_proxy_command(); return exit_status; } static void control_persist_detach(void) { pid_t pid; int devnull; debug("%s: backgrounding master process", __func__); /* * master (current process) into the background, and make the * foreground process a client of the backgrounded master. */ switch ((pid = fork())) { case -1: fatal("%s: fork: %s", __func__, strerror(errno)); case 0: /* Child: master process continues mainloop */ break; default: /* Parent: set up mux slave to connect to backgrounded master */ debug2("%s: background process is %ld", __func__, (long)pid); stdin_null_flag = ostdin_null_flag; no_shell_flag = ono_shell_flag; no_tty_flag = ono_tty_flag; tty_flag = otty_flag; close(muxserver_sock); muxserver_sock = -1; options.control_master = SSHCTL_MASTER_NO; muxclient(options.control_path); /* muxclient() doesn't return on success. */ fatal("Failed to connect to new control master"); } if ((devnull = open(_PATH_DEVNULL, O_RDWR)) == -1) { error("%s: open(\"/dev/null\"): %s", __func__, strerror(errno)); } else { if (dup2(devnull, STDIN_FILENO) == -1 || dup2(devnull, STDOUT_FILENO) == -1) error("%s: dup2: %s", __func__, strerror(errno)); if (devnull > STDERR_FILENO) close(devnull); } } /* Do fork() after authentication. Used by "ssh -f" */ static void fork_postauth(void) { if (need_controlpersist_detach) control_persist_detach(); debug("forking to background"); fork_after_authentication_flag = 0; if (daemon(1, 1) < 0) fatal("daemon() failed: %.200s", strerror(errno)); } /* Callback for remote forward global requests */ static void ssh_confirm_remote_forward(int type, u_int32_t seq, void *ctxt) { Forward *rfwd = (Forward *)ctxt; /* XXX verbose() on failure? */ debug("remote forward %s for: listen %d, connect %s:%d", type == SSH2_MSG_REQUEST_SUCCESS ? "success" : "failure", rfwd->listen_port, rfwd->connect_host, rfwd->connect_port); if (type == SSH2_MSG_REQUEST_SUCCESS && rfwd->listen_port == 0) { rfwd->allocated_port = packet_get_int(); logit("Allocated port %u for remote forward to %s:%d", rfwd->allocated_port, rfwd->connect_host, rfwd->connect_port); } if (type == SSH2_MSG_REQUEST_FAILURE) { if (options.exit_on_forward_failure) fatal("Error: remote port forwarding failed for " "listen port %d", rfwd->listen_port); else logit("Warning: remote port forwarding failed for " "listen port %d", rfwd->listen_port); } if (++remote_forward_confirms_received == options.num_remote_forwards) { debug("All remote forwarding requests processed"); if (fork_after_authentication_flag) fork_postauth(); } } static void client_cleanup_stdio_fwd(int id, void *arg) { debug("stdio forwarding: done"); cleanup_exit(0); } static int client_setup_stdio_fwd(const char *host_to_connect, u_short port_to_connect) { Channel *c; int in, out; debug3("client_setup_stdio_fwd %s:%d", host_to_connect, port_to_connect); in = dup(STDIN_FILENO); out = dup(STDOUT_FILENO); if (in < 0 || out < 0) fatal("channel_connect_stdio_fwd: dup() in/out failed"); if ((c = channel_connect_stdio_fwd(host_to_connect, port_to_connect, in, out)) == NULL) return 0; channel_register_cleanup(c->self, client_cleanup_stdio_fwd, 0); return 1; } static void ssh_init_forwarding(void) { int success = 0; int i; if (stdio_forward_host != NULL) { if (!compat20) { fatal("stdio forwarding require Protocol 2"); } if (!client_setup_stdio_fwd(stdio_forward_host, stdio_forward_port)) fatal("Failed to connect in stdio forward mode."); } /* Initiate local TCP/IP port forwardings. */ for (i = 0; i < options.num_local_forwards; i++) { debug("Local connections to %.200s:%d forwarded to remote " "address %.200s:%d", (options.local_forwards[i].listen_host == NULL) ? (options.gateway_ports ? "*" : "LOCALHOST") : options.local_forwards[i].listen_host, options.local_forwards[i].listen_port, options.local_forwards[i].connect_host, options.local_forwards[i].connect_port); success += channel_setup_local_fwd_listener( options.local_forwards[i].listen_host, options.local_forwards[i].listen_port, options.local_forwards[i].connect_host, options.local_forwards[i].connect_port, options.gateway_ports); } if (i > 0 && success != i && options.exit_on_forward_failure) fatal("Could not request local forwarding."); if (i > 0 && success == 0) error("Could not request local forwarding."); /* Initiate remote TCP/IP port forwardings. */ for (i = 0; i < options.num_remote_forwards; i++) { debug("Remote connections from %.200s:%d forwarded to " "local address %.200s:%d", (options.remote_forwards[i].listen_host == NULL) ? "LOCALHOST" : options.remote_forwards[i].listen_host, options.remote_forwards[i].listen_port, options.remote_forwards[i].connect_host, options.remote_forwards[i].connect_port); if (channel_request_remote_forwarding( options.remote_forwards[i].listen_host, options.remote_forwards[i].listen_port, options.remote_forwards[i].connect_host, options.remote_forwards[i].connect_port) < 0) { if (options.exit_on_forward_failure) fatal("Could not request remote forwarding."); else logit("Warning: Could not request remote " "forwarding."); } client_register_global_confirm(ssh_confirm_remote_forward, &options.remote_forwards[i]); } /* Initiate tunnel forwarding. */ if (options.tun_open != SSH_TUNMODE_NO) { if (client_request_tun_fwd(options.tun_open, options.tun_local, options.tun_remote) == -1) { if (options.exit_on_forward_failure) fatal("Could not request tunnel forwarding."); else error("Could not request tunnel forwarding."); } } } static void check_agent_present(void) { if (options.forward_agent) { /* Clear agent forwarding if we don't have an agent. */ if (!ssh_agent_present()) options.forward_agent = 0; } } static int ssh_session(void) { int type; int interactive = 0; int have_tty = 0; struct winsize ws; char *cp; const char *display; /* Enable compression if requested. */ if (options.compression) { debug("Requesting compression at level %d.", options.compression_level); if (options.compression_level < 1 || options.compression_level > 9) fatal("Compression level must be from 1 (fast) to " "9 (slow, best)."); /* Send the request. */ packet_start(SSH_CMSG_REQUEST_COMPRESSION); packet_put_int(options.compression_level); packet_send(); packet_write_wait(); type = packet_read(); if (type == SSH_SMSG_SUCCESS) packet_start_compression(options.compression_level); else if (type == SSH_SMSG_FAILURE) logit("Warning: Remote host refused compression."); else packet_disconnect("Protocol error waiting for " "compression response."); } /* Allocate a pseudo tty if appropriate. */ if (tty_flag) { debug("Requesting pty."); /* Start the packet. */ packet_start(SSH_CMSG_REQUEST_PTY); /* Store TERM in the packet. There is no limit on the length of the string. */ cp = getenv("TERM"); if (!cp) cp = ""; packet_put_cstring(cp); /* Store window size in the packet. */ if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) < 0) memset(&ws, 0, sizeof(ws)); packet_put_int((u_int)ws.ws_row); packet_put_int((u_int)ws.ws_col); packet_put_int((u_int)ws.ws_xpixel); packet_put_int((u_int)ws.ws_ypixel); /* Store tty modes in the packet. */ tty_make_modes(fileno(stdin), NULL); /* Send the packet, and wait for it to leave. */ packet_send(); packet_write_wait(); /* Read response from the server. */ type = packet_read(); if (type == SSH_SMSG_SUCCESS) { interactive = 1; have_tty = 1; } else if (type == SSH_SMSG_FAILURE) logit("Warning: Remote host failed or refused to " "allocate a pseudo tty."); else packet_disconnect("Protocol error waiting for pty " "request response."); } /* Request X11 forwarding if enabled and DISPLAY is set. */ display = getenv("DISPLAY"); if (options.forward_x11 && display != NULL) { char *proto, *data; /* Get reasonable local authentication information. */ client_x11_get_proto(display, options.xauth_location, options.forward_x11_trusted, options.forward_x11_timeout, &proto, &data); /* Request forwarding with authentication spoofing. */ debug("Requesting X11 forwarding with authentication " "spoofing."); x11_request_forwarding_with_spoofing(0, display, proto, data); /* Read response from the server. */ type = packet_read(); if (type == SSH_SMSG_SUCCESS) { interactive = 1; } else if (type == SSH_SMSG_FAILURE) { logit("Warning: Remote host denied X11 forwarding."); } else { packet_disconnect("Protocol error waiting for X11 " "forwarding"); } } /* Tell the packet module whether this is an interactive session. */ packet_set_interactive(interactive, options.ip_qos_interactive, options.ip_qos_bulk); /* Request authentication agent forwarding if appropriate. */ check_agent_present(); if (options.forward_agent) { debug("Requesting authentication agent forwarding."); auth_request_forwarding(); /* Read response from the server. */ type = packet_read(); packet_check_eom(); if (type != SSH_SMSG_SUCCESS) logit("Warning: Remote host denied authentication agent forwarding."); } /* Initiate port forwardings. */ ssh_init_forwarding(); /* Execute a local command */ if (options.local_command != NULL && options.permit_local_command) ssh_local_cmd(options.local_command); /* * If requested and we are not interested in replies to remote * forwarding requests, then let ssh continue in the background. */ if (fork_after_authentication_flag) { if (options.exit_on_forward_failure && options.num_remote_forwards > 0) { debug("deferring postauth fork until remote forward " "confirmation received"); } else fork_postauth(); } /* * If a command was specified on the command line, execute the * command now. Otherwise request the server to start a shell. */ if (buffer_len(&command) > 0) { int len = buffer_len(&command); if (len > 900) len = 900; debug("Sending command: %.*s", len, (u_char *)buffer_ptr(&command)); packet_start(SSH_CMSG_EXEC_CMD); packet_put_string(buffer_ptr(&command), buffer_len(&command)); packet_send(); packet_write_wait(); } else { debug("Requesting shell."); packet_start(SSH_CMSG_EXEC_SHELL); packet_send(); packet_write_wait(); } /* Enter the interactive session. */ return client_loop(have_tty, tty_flag ? options.escape_char : SSH_ESCAPECHAR_NONE, 0); } /* request pty/x11/agent/tcpfwd/shell for channel */ static void ssh_session2_setup(int id, int success, void *arg) { extern char **environ; const char *display; int interactive = tty_flag; if (!success) return; /* No need for error message, channels code sens one */ display = getenv("DISPLAY"); if (options.forward_x11 && display != NULL) { char *proto, *data; /* Get reasonable local authentication information. */ client_x11_get_proto(display, options.xauth_location, options.forward_x11_trusted, options.forward_x11_timeout, &proto, &data); /* Request forwarding with authentication spoofing. */ debug("Requesting X11 forwarding with authentication " "spoofing."); x11_request_forwarding_with_spoofing(id, display, proto, data); interactive = 1; /* XXX wait for reply */ } check_agent_present(); if (options.forward_agent) { debug("Requesting authentication agent forwarding."); channel_request_start(id, "auth-agent-req@openssh.com", 0); packet_send(); } client_session2_setup(id, tty_flag, subsystem_flag, getenv("TERM"), NULL, fileno(stdin), &command, environ); } /* open new channel for a session */ static int ssh_session2_open(void) { Channel *c; int window, packetmax, in, out, err; if (stdin_null_flag) { in = open(_PATH_DEVNULL, O_RDONLY); } else { in = dup(STDIN_FILENO); } out = dup(STDOUT_FILENO); err = dup(STDERR_FILENO); if (in < 0 || out < 0 || err < 0) fatal("dup() in/out/err failed"); /* enable nonblocking unless tty */ if (!isatty(in)) set_nonblock(in); if (!isatty(out)) set_nonblock(out); if (!isatty(err)) set_nonblock(err); - window = CHAN_SES_WINDOW_DEFAULT; + /* + * We need to check to see what to do about buffer sizes here. + * - In an HPN to non-HPN connection we want to limit the window size to + * something reasonable in case the far side has the large window bug. + * - In an HPN to HPN connection we want to use the max window size but + * allow the user to override it. + * - Lastly if HPN is disabled then use the ssh standard window size. + * + * We cannot just do a getsockopt() here and set the ssh window to that + * as in case of autotuning of socket buffers the window would get stuck + * at the initial buffer size, generally less than 96k. Therefore we + * need to set the maximum ssh window size to the maximum HPN buffer + * size unless the user has set TcpRcvBufPoll to no. In that case we + * can just set the window to the minimum of HPN buffer size and TCP + * receive buffer size. + */ + if (tty_flag) + options.hpn_buffer_size = CHAN_SES_WINDOW_DEFAULT; + else + options.hpn_buffer_size = CHAN_HPN_MIN_WINDOW_DEFAULT; + + if (datafellows & SSH_BUG_LARGEWINDOW) { + debug("HPN to Non-HPN Connection"); + } else if (options.tcp_rcv_buf_poll <= 0) { + sock_get_rcvbuf(&options.hpn_buffer_size, 0); + debug("HPNBufferSize set to TCP RWIN: %d", + options.hpn_buffer_size); + } else if (options.tcp_rcv_buf > 0) { + sock_get_rcvbuf(&options.hpn_buffer_size, + options.tcp_rcv_buf); + debug("HPNBufferSize set to user TCPRcvBuf: %d", + options.hpn_buffer_size); + } + debug("Final hpn_buffer_size = %d", options.hpn_buffer_size); + channel_set_hpn(options.hpn_disabled, options.hpn_buffer_size); + window = options.hpn_buffer_size; + packetmax = CHAN_SES_PACKET_DEFAULT; if (tty_flag) { + window = CHAN_SES_WINDOW_DEFAULT; window >>= 1; packetmax >>= 1; } c = channel_new( "session", SSH_CHANNEL_OPENING, in, out, err, window, packetmax, CHAN_EXTENDED_WRITE, "client-session", /*nonblock*/0); - + if (!options.hpn_disabled && options.tcp_rcv_buf_poll > 0) { + c->dynamic_window = 1; + debug("Enabled Dynamic Window Scaling\n"); + } debug3("ssh_session2_open: channel_new: %d", c->self); channel_send_open(c->self); if (!no_shell_flag) channel_register_open_confirm(c->self, ssh_session2_setup, NULL); return c->self; } static int ssh_session2(void) { int id = -1; /* XXX should be pre-session */ ssh_init_forwarding(); /* Start listening for multiplex clients */ muxserver_listen(); /* * If we are in control persist mode, then prepare to background * ourselves and have a foreground client attach as a control * slave. NB. we must save copies of the flags that we override for * the backgrounding, since we defer attachment of the slave until * after the connection is fully established (in particular, * async rfwd replies have been received for ExitOnForwardFailure). */ if (options.control_persist && muxserver_sock != -1) { ostdin_null_flag = stdin_null_flag; ono_shell_flag = no_shell_flag; ono_tty_flag = no_tty_flag; otty_flag = tty_flag; stdin_null_flag = 1; no_shell_flag = 1; no_tty_flag = 1; tty_flag = 0; if (!fork_after_authentication_flag) need_controlpersist_detach = 1; fork_after_authentication_flag = 1; } if (!no_shell_flag || (datafellows & SSH_BUG_DUMMYCHAN)) id = ssh_session2_open(); /* If we don't expect to open a new session, then disallow it */ if (options.control_master == SSHCTL_MASTER_NO && (datafellows & SSH_NEW_OPENSSH)) { debug("Requesting no-more-sessions@openssh.com"); packet_start(SSH2_MSG_GLOBAL_REQUEST); packet_put_cstring("no-more-sessions@openssh.com"); packet_put_char(0); packet_send(); } /* Execute a local command */ if (options.local_command != NULL && options.permit_local_command) ssh_local_cmd(options.local_command); /* * If requested and we are not interested in replies to remote * forwarding requests, then let ssh continue in the background. */ if (fork_after_authentication_flag) { if (options.exit_on_forward_failure && options.num_remote_forwards > 0) { debug("deferring postauth fork until remote forward " "confirmation received"); } else fork_postauth(); } if (options.use_roaming) request_roaming(); return client_loop(tty_flag, tty_flag ? options.escape_char : SSH_ESCAPECHAR_NONE, id); } static void load_public_identity_files(void) { char *filename, *cp, thishost[NI_MAXHOST]; char *pwdir = NULL, *pwname = NULL; int i = 0; Key *public; struct passwd *pw; u_int n_ids; char *identity_files[SSH_MAX_IDENTITY_FILES]; Key *identity_keys[SSH_MAX_IDENTITY_FILES]; #ifdef ENABLE_PKCS11 Key **keys; int nkeys; #endif /* PKCS11 */ n_ids = 0; bzero(identity_files, sizeof(identity_files)); bzero(identity_keys, sizeof(identity_keys)); #ifdef ENABLE_PKCS11 if (options.pkcs11_provider != NULL && options.num_identity_files < SSH_MAX_IDENTITY_FILES && (pkcs11_init(!options.batch_mode) == 0) && (nkeys = pkcs11_add_provider(options.pkcs11_provider, NULL, &keys)) > 0) { for (i = 0; i < nkeys; i++) { if (n_ids >= SSH_MAX_IDENTITY_FILES) { key_free(keys[i]); continue; } identity_keys[n_ids] = keys[i]; identity_files[n_ids] = xstrdup(options.pkcs11_provider); /* XXX */ n_ids++; } xfree(keys); } #endif /* ENABLE_PKCS11 */ if ((pw = getpwuid(original_real_uid)) == NULL) fatal("load_public_identity_files: getpwuid failed"); pwname = xstrdup(pw->pw_name); pwdir = xstrdup(pw->pw_dir); if (gethostname(thishost, sizeof(thishost)) == -1) fatal("load_public_identity_files: gethostname: %s", strerror(errno)); for (i = 0; i < options.num_identity_files; i++) { if (n_ids >= SSH_MAX_IDENTITY_FILES) { xfree(options.identity_files[i]); continue; } cp = tilde_expand_filename(options.identity_files[i], original_real_uid); filename = percent_expand(cp, "d", pwdir, "u", pwname, "l", thishost, "h", host, "r", options.user, (char *)NULL); xfree(cp); public = key_load_public(filename, NULL); debug("identity file %s type %d", filename, public ? public->type : -1); xfree(options.identity_files[i]); identity_files[n_ids] = filename; identity_keys[n_ids] = public; if (++n_ids >= SSH_MAX_IDENTITY_FILES) continue; /* Try to add the certificate variant too */ xasprintf(&cp, "%s-cert", filename); public = key_load_public(cp, NULL); debug("identity file %s type %d", cp, public ? public->type : -1); if (public == NULL) { xfree(cp); continue; } if (!key_is_cert(public)) { debug("%s: key %s type %s is not a certificate", __func__, cp, key_type(public)); key_free(public); xfree(cp); continue; } identity_keys[n_ids] = public; /* point to the original path, most likely the private key */ identity_files[n_ids] = xstrdup(filename); n_ids++; } options.num_identity_files = n_ids; memcpy(options.identity_files, identity_files, sizeof(identity_files)); memcpy(options.identity_keys, identity_keys, sizeof(identity_keys)); bzero(pwname, strlen(pwname)); xfree(pwname); bzero(pwdir, strlen(pwdir)); xfree(pwdir); } static void main_sigchld_handler(int sig) { int save_errno = errno; pid_t pid; int status; while ((pid = waitpid(-1, &status, WNOHANG)) > 0 || (pid < 0 && errno == EINTR)) ; signal(sig, main_sigchld_handler); errno = save_errno; } diff --git a/crypto/openssh/sshconnect.c b/crypto/openssh/sshconnect.c index 3e55b5dd09a0..0615b44f1496 100644 --- a/crypto/openssh/sshconnect.c +++ b/crypto/openssh/sshconnect.c @@ -1,1293 +1,1321 @@ /* $OpenBSD: sshconnect.c,v 1.232 2011/01/16 11:50:36 djm Exp $ */ /* $FreeBSD$ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved * Code to connect to a remote host, and to perform the client side of the * login (authentication) dialog. * * As far as I am concerned, the code I have written for this software * can be used freely for any purpose. Any derived versions of this * software must be clearly marked as such, and if the derived work is * incompatible with the protocol description in the RFC file, it must be * called by a name other than "ssh" or "Secure Shell". */ #include "includes.h" #include #include #include #include #ifdef HAVE_SYS_TIME_H # include #endif #include #include #include #include #include #include #ifdef HAVE_PATHS_H #include #endif #include #include #include #include #include #include #include #include "xmalloc.h" #include "key.h" #include "hostfile.h" #include "ssh.h" #include "rsa.h" #include "buffer.h" #include "packet.h" #include "uidswap.h" #include "compat.h" #include "key.h" #include "sshconnect.h" #include "hostfile.h" #include "log.h" #include "readconf.h" #include "atomicio.h" #include "misc.h" #include "dns.h" #include "roaming.h" #include "ssh2.h" #include "version.h" char *client_version_string = NULL; char *server_version_string = NULL; static int matching_host_key_dns = 0; static pid_t proxy_command_pid = 0; /* import */ extern Options options; extern char *__progname; extern uid_t original_real_uid; extern uid_t original_effective_uid; static int show_other_keys(struct hostkeys *, Key *); static void warn_changed_key(Key *); /* * Connect to the given ssh server using a proxy command. */ static int ssh_proxy_connect(const char *host, u_short port, const char *proxy_command) { char *command_string, *tmp; int pin[2], pout[2]; pid_t pid; char *shell, strport[NI_MAXSERV]; if ((shell = getenv("SHELL")) == NULL || *shell == '\0') shell = _PATH_BSHELL; /* Convert the port number into a string. */ snprintf(strport, sizeof strport, "%hu", port); /* * Build the final command string in the buffer by making the * appropriate substitutions to the given proxy command. * * Use "exec" to avoid "sh -c" processes on some platforms * (e.g. Solaris) */ xasprintf(&tmp, "exec %s", proxy_command); command_string = percent_expand(tmp, "h", host, "p", strport, "r", options.user, (char *)NULL); xfree(tmp); /* Create pipes for communicating with the proxy. */ if (pipe(pin) < 0 || pipe(pout) < 0) fatal("Could not create pipes to communicate with the proxy: %.100s", strerror(errno)); debug("Executing proxy command: %.500s", command_string); /* Fork and execute the proxy command. */ if ((pid = fork()) == 0) { char *argv[10]; /* Child. Permanently give up superuser privileges. */ permanently_drop_suid(original_real_uid); /* Redirect stdin and stdout. */ close(pin[1]); if (pin[0] != 0) { if (dup2(pin[0], 0) < 0) perror("dup2 stdin"); close(pin[0]); } close(pout[0]); if (dup2(pout[1], 1) < 0) perror("dup2 stdout"); /* Cannot be 1 because pin allocated two descriptors. */ close(pout[1]); /* Stderr is left as it is so that error messages get printed on the user's terminal. */ argv[0] = shell; argv[1] = "-c"; argv[2] = command_string; argv[3] = NULL; /* Execute the proxy command. Note that we gave up any extra privileges above. */ signal(SIGPIPE, SIG_DFL); execv(argv[0], argv); perror(argv[0]); exit(1); } /* Parent. */ if (pid < 0) fatal("fork failed: %.100s", strerror(errno)); else proxy_command_pid = pid; /* save pid to clean up later */ /* Close child side of the descriptors. */ close(pin[0]); close(pout[1]); /* Free the command name. */ xfree(command_string); /* Set the connection file descriptors. */ packet_set_connection(pout[0], pin[1]); packet_set_timeout(options.server_alive_interval, options.server_alive_count_max); /* Indicate OK return */ return 0; } void ssh_kill_proxy_command(void) { /* * Send SIGHUP to proxy command if used. We don't wait() in * case it hangs and instead rely on init to reap the child */ if (proxy_command_pid > 1) kill(proxy_command_pid, SIGHUP); } +/* + * Set TCP receive buffer if requested. + * Note: tuning needs to happen after the socket is created but before the + * connection happens so winscale is negotiated properly. + */ +static void +ssh_set_socket_recvbuf(int sock) +{ + void *buf = (void *)&options.tcp_rcv_buf; + int socksize, sz = sizeof(options.tcp_rcv_buf); + socklen_t len = sizeof(int); + + debug("setsockopt attempting to set SO_RCVBUF to %d", + options.tcp_rcv_buf); + if (setsockopt(sock, SOL_SOCKET, SO_RCVBUF, buf, sz) >= 0) { + getsockopt(sock, SOL_SOCKET, SO_RCVBUF, &socksize, &len); + debug("setsockopt SO_RCVBUF: %.100s %d", strerror(errno), + socksize); + } else + error("Couldn't set socket receive buffer to %d: %.100s", + options.tcp_rcv_buf, strerror(errno)); +} + /* * Creates a (possibly privileged) socket for use as the ssh connection. */ static int ssh_create_socket(int privileged, struct addrinfo *ai) { int sock, gaierr; struct addrinfo hints, *res; /* * If we are running as root and want to connect to a privileged * port, bind our own socket to a privileged port. */ if (privileged) { int p = IPPORT_RESERVED - 1; PRIV_START; sock = rresvport_af(&p, ai->ai_family); PRIV_END; if (sock < 0) error("rresvport: af=%d %.100s", ai->ai_family, strerror(errno)); else debug("Allocated local port %d.", p); + if (options.tcp_rcv_buf > 0) + ssh_set_socket_recvbuf(sock); return sock; } sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); if (sock < 0) { error("socket: %.100s", strerror(errno)); return -1; } fcntl(sock, F_SETFD, FD_CLOEXEC); + if (options.tcp_rcv_buf > 0) + ssh_set_socket_recvbuf(sock); + /* Bind the socket to an alternative local IP address */ if (options.bind_address == NULL) return sock; memset(&hints, 0, sizeof(hints)); hints.ai_family = ai->ai_family; hints.ai_socktype = ai->ai_socktype; hints.ai_protocol = ai->ai_protocol; hints.ai_flags = AI_PASSIVE; gaierr = getaddrinfo(options.bind_address, NULL, &hints, &res); if (gaierr) { error("getaddrinfo: %s: %s", options.bind_address, ssh_gai_strerror(gaierr)); close(sock); return -1; } if (bind(sock, res->ai_addr, res->ai_addrlen) < 0) { error("bind: %s: %s", options.bind_address, strerror(errno)); close(sock); freeaddrinfo(res); return -1; } freeaddrinfo(res); return sock; } static int timeout_connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen, int *timeoutp) { fd_set *fdset; struct timeval tv, t_start; socklen_t optlen; int optval, rc, result = -1; gettimeofday(&t_start, NULL); if (*timeoutp <= 0) { result = connect(sockfd, serv_addr, addrlen); goto done; } set_nonblock(sockfd); rc = connect(sockfd, serv_addr, addrlen); if (rc == 0) { unset_nonblock(sockfd); result = 0; goto done; } if (errno != EINPROGRESS) { result = -1; goto done; } fdset = (fd_set *)xcalloc(howmany(sockfd + 1, NFDBITS), sizeof(fd_mask)); FD_SET(sockfd, fdset); ms_to_timeval(&tv, *timeoutp); for (;;) { rc = select(sockfd + 1, NULL, fdset, NULL, &tv); if (rc != -1 || errno != EINTR) break; } switch (rc) { case 0: /* Timed out */ errno = ETIMEDOUT; break; case -1: /* Select error */ debug("select: %s", strerror(errno)); break; case 1: /* Completed or failed */ optval = 0; optlen = sizeof(optval); if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &optval, &optlen) == -1) { debug("getsockopt: %s", strerror(errno)); break; } if (optval != 0) { errno = optval; break; } result = 0; unset_nonblock(sockfd); break; default: /* Should not occur */ fatal("Bogus return (%d) from select()", rc); } xfree(fdset); done: if (result == 0 && *timeoutp > 0) { ms_subtract_diff(&t_start, timeoutp); if (*timeoutp <= 0) { errno = ETIMEDOUT; result = -1; } } return (result); } /* * Opens a TCP/IP connection to the remote server on the given host. * The address of the remote host will be returned in hostaddr. * If port is 0, the default port will be used. If needpriv is true, * a privileged port will be allocated to make the connection. * This requires super-user privileges if needpriv is true. * Connection_attempts specifies the maximum number of tries (one per * second). If proxy_command is non-NULL, it specifies the command (with %h * and %p substituted for host and port, respectively) to use to contact * the daemon. */ int ssh_connect(const char *host, struct sockaddr_storage * hostaddr, u_short port, int family, int connection_attempts, int *timeout_ms, int want_keepalive, int needpriv, const char *proxy_command) { int gaierr; int on = 1; int sock = -1, attempt; char ntop[NI_MAXHOST], strport[NI_MAXSERV]; struct addrinfo hints, *ai, *aitop; debug2("ssh_connect: needpriv %d", needpriv); /* If a proxy command is given, connect using it. */ if (proxy_command != NULL) return ssh_proxy_connect(host, port, proxy_command); /* No proxy command. */ memset(&hints, 0, sizeof(hints)); hints.ai_family = family; hints.ai_socktype = SOCK_STREAM; snprintf(strport, sizeof strport, "%u", port); if ((gaierr = getaddrinfo(host, strport, &hints, &aitop)) != 0) fatal("%s: Could not resolve hostname %.100s: %s", __progname, host, ssh_gai_strerror(gaierr)); for (attempt = 0; attempt < connection_attempts; attempt++) { if (attempt > 0) { /* Sleep a moment before retrying. */ sleep(1); debug("Trying again..."); } /* * Loop through addresses for this host, and try each one in * sequence until the connection succeeds. */ for (ai = aitop; ai; ai = ai->ai_next) { if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) continue; if (getnameinfo(ai->ai_addr, ai->ai_addrlen, ntop, sizeof(ntop), strport, sizeof(strport), NI_NUMERICHOST|NI_NUMERICSERV) != 0) { error("ssh_connect: getnameinfo failed"); continue; } debug("Connecting to %.200s [%.100s] port %s.", host, ntop, strport); /* Create a socket for connecting. */ sock = ssh_create_socket(needpriv, ai); if (sock < 0) /* Any error is already output */ continue; if (timeout_connect(sock, ai->ai_addr, ai->ai_addrlen, timeout_ms) >= 0) { /* Successful connection. */ memcpy(hostaddr, ai->ai_addr, ai->ai_addrlen); break; } else { debug("connect to address %s port %s: %s", ntop, strport, strerror(errno)); close(sock); sock = -1; } } if (sock != -1) break; /* Successful connection. */ } freeaddrinfo(aitop); /* Return failure if we didn't get a successful connection. */ if (sock == -1) { error("ssh: connect to host %s port %s: %s", host, strport, strerror(errno)); return (-1); } debug("Connection established."); /* Set SO_KEEPALIVE if requested. */ if (want_keepalive && setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, sizeof(on)) < 0) error("setsockopt SO_KEEPALIVE: %.100s", strerror(errno)); /* Set the connection. */ packet_set_connection(sock, sock); packet_set_timeout(options.server_alive_interval, options.server_alive_count_max); return 0; } /* * Waits for the server identification string, and sends our own * identification string. */ void ssh_exchange_identification(int timeout_ms) { char buf[256], remote_version[256]; /* must be same size! */ int remote_major, remote_minor, mismatch; int connection_in = packet_get_connection_in(); int connection_out = packet_get_connection_out(); int minor1 = PROTOCOL_MINOR_1; u_int i, n; size_t len; int fdsetsz, remaining, rc; struct timeval t_start, t_remaining; fd_set *fdset; fdsetsz = howmany(connection_in + 1, NFDBITS) * sizeof(fd_mask); fdset = xcalloc(1, fdsetsz); /* Read other side's version identification. */ remaining = timeout_ms; for (n = 0;;) { for (i = 0; i < sizeof(buf) - 1; i++) { if (timeout_ms > 0) { gettimeofday(&t_start, NULL); ms_to_timeval(&t_remaining, remaining); FD_SET(connection_in, fdset); rc = select(connection_in + 1, fdset, NULL, fdset, &t_remaining); ms_subtract_diff(&t_start, &remaining); if (rc == 0 || remaining <= 0) fatal("Connection timed out during " "banner exchange"); if (rc == -1) { if (errno == EINTR) continue; fatal("ssh_exchange_identification: " "select: %s", strerror(errno)); } } len = roaming_atomicio(read, connection_in, &buf[i], 1); if (len != 1 && errno == EPIPE) fatal("ssh_exchange_identification: " "Connection closed by remote host"); else if (len != 1) fatal("ssh_exchange_identification: " "read: %.100s", strerror(errno)); if (buf[i] == '\r') { buf[i] = '\n'; buf[i + 1] = 0; continue; /**XXX wait for \n */ } if (buf[i] == '\n') { buf[i + 1] = 0; break; } if (++n > 65536) fatal("ssh_exchange_identification: " "No banner received"); } buf[sizeof(buf) - 1] = 0; if (strncmp(buf, "SSH-", 4) == 0) break; debug("ssh_exchange_identification: %s", buf); } server_version_string = xstrdup(buf); xfree(fdset); /* * Check that the versions match. In future this might accept * several versions and set appropriate flags to handle them. */ if (sscanf(server_version_string, "SSH-%d.%d-%[^\n]\n", &remote_major, &remote_minor, remote_version) != 3) fatal("Bad remote protocol version identification: '%.100s'", buf); debug("Remote protocol version %d.%d, remote software version %.100s", remote_major, remote_minor, remote_version); compat_datafellows(remote_version); mismatch = 0; switch (remote_major) { case 1: if (remote_minor == 99 && (options.protocol & SSH_PROTO_2) && !(options.protocol & SSH_PROTO_1_PREFERRED)) { enable_compat20(); break; } if (!(options.protocol & SSH_PROTO_1)) { mismatch = 1; break; } if (remote_minor < 3) { fatal("Remote machine has too old SSH software version."); } else if (remote_minor == 3 || remote_minor == 4) { /* We speak 1.3, too. */ enable_compat13(); minor1 = 3; if (options.forward_agent) { logit("Agent forwarding disabled for protocol 1.3"); options.forward_agent = 0; } } break; case 2: if (options.protocol & SSH_PROTO_2) { enable_compat20(); break; } /* FALLTHROUGH */ default: mismatch = 1; break; } if (mismatch) fatal("Protocol major versions differ: %d vs. %d", (options.protocol & SSH_PROTO_2) ? PROTOCOL_MAJOR_2 : PROTOCOL_MAJOR_1, remote_major); /* Send our own protocol version identification. */ snprintf(buf, sizeof buf, "SSH-%d.%d-%.100s%s", compat20 ? PROTOCOL_MAJOR_2 : PROTOCOL_MAJOR_1, compat20 ? PROTOCOL_MINOR_2 : minor1, - SSH_VERSION, compat20 ? "\r\n" : "\n"); + SSH_RELEASE, compat20 ? "\r\n" : "\n"); if (roaming_atomicio(vwrite, connection_out, buf, strlen(buf)) != strlen(buf)) fatal("write: %.100s", strerror(errno)); client_version_string = xstrdup(buf); chop(client_version_string); chop(server_version_string); debug("Local version string %.100s", client_version_string); } /* defaults to 'no' */ static int confirm(const char *prompt) { const char *msg, *again = "Please type 'yes' or 'no': "; char *p; int ret = -1; if (options.batch_mode) return 0; for (msg = prompt;;msg = again) { p = read_passphrase(msg, RP_ECHO); if (p == NULL || (p[0] == '\0') || (p[0] == '\n') || strncasecmp(p, "no", 2) == 0) ret = 0; if (p && strncasecmp(p, "yes", 3) == 0) ret = 1; if (p) xfree(p); if (ret != -1) return ret; } } static int check_host_cert(const char *host, const Key *host_key) { const char *reason; if (key_cert_check_authority(host_key, 1, 0, host, &reason) != 0) { error("%s", reason); return 0; } if (buffer_len(&host_key->cert->critical) != 0) { error("Certificate for %s contains unsupported " "critical options(s)", host); return 0; } return 1; } static int sockaddr_is_local(struct sockaddr *hostaddr) { switch (hostaddr->sa_family) { case AF_INET: return (ntohl(((struct sockaddr_in *)hostaddr)-> sin_addr.s_addr) >> 24) == IN_LOOPBACKNET; case AF_INET6: return IN6_IS_ADDR_LOOPBACK( &(((struct sockaddr_in6 *)hostaddr)->sin6_addr)); default: return 0; } } /* * Prepare the hostname and ip address strings that are used to lookup * host keys in known_hosts files. These may have a port number appended. */ void get_hostfile_hostname_ipaddr(char *hostname, struct sockaddr *hostaddr, u_short port, char **hostfile_hostname, char **hostfile_ipaddr) { char ntop[NI_MAXHOST]; socklen_t addrlen; switch (hostaddr == NULL ? -1 : hostaddr->sa_family) { case -1: addrlen = 0; break; case AF_INET: addrlen = sizeof(struct sockaddr_in); break; case AF_INET6: addrlen = sizeof(struct sockaddr_in6); break; default: addrlen = sizeof(struct sockaddr); break; } /* * We don't have the remote ip-address for connections * using a proxy command */ if (hostfile_ipaddr != NULL) { if (options.proxy_command == NULL) { if (getnameinfo(hostaddr, addrlen, ntop, sizeof(ntop), NULL, 0, NI_NUMERICHOST) != 0) fatal("check_host_key: getnameinfo failed"); *hostfile_ipaddr = put_host_port(ntop, port); } else { *hostfile_ipaddr = xstrdup(""); } } /* * Allow the user to record the key under a different name or * differentiate a non-standard port. This is useful for ssh * tunneling over forwarded connections or if you run multiple * sshd's on different ports on the same machine. */ if (hostfile_hostname != NULL) { if (options.host_key_alias != NULL) { *hostfile_hostname = xstrdup(options.host_key_alias); debug("using hostkeyalias: %s", *hostfile_hostname); } else { *hostfile_hostname = put_host_port(hostname, port); } } } /* * check whether the supplied host key is valid, return -1 if the key * is not valid. the user_hostfile will not be updated if 'readonly' is true. */ #define RDRW 0 #define RDONLY 1 #define ROQUIET 2 static int check_host_key(char *hostname, struct sockaddr *hostaddr, u_short port, Key *host_key, int readonly, char *user_hostfile, char *system_hostfile) { Key *raw_key = NULL; const char *type; char *ip = NULL, *host = NULL; char hostline[1000], *hostp, *fp, *ra; HostStatus host_status; HostStatus ip_status; int r, want_cert = key_is_cert(host_key), host_ip_differ = 0; int local = sockaddr_is_local(hostaddr); char msg[1024]; int len, cancelled_forwarding = 0; struct hostkeys *host_hostkeys, *ip_hostkeys; const struct hostkey_entry *host_found, *ip_found; /* * Force accepting of the host key for loopback/localhost. The * problem is that if the home directory is NFS-mounted to multiple * machines, localhost will refer to a different machine in each of * them, and the user will get bogus HOST_CHANGED warnings. This * essentially disables host authentication for localhost; however, * this is probably not a real problem. */ if (options.no_host_authentication_for_localhost == 1 && local && options.host_key_alias == NULL) { debug("Forcing accepting of host key for " "loopback/localhost."); return 0; } /* * Prepare the hostname and address strings used for hostkey lookup. * In some cases, these will have a port number appended. */ get_hostfile_hostname_ipaddr(hostname, hostaddr, port, &host, &ip); /* * Turn off check_host_ip if the connection is to localhost, via proxy * command or if we don't have a hostname to compare with */ if (options.check_host_ip && (local || strcmp(hostname, ip) == 0 || options.proxy_command != NULL)) options.check_host_ip = 0; host_hostkeys = init_hostkeys(); load_hostkeys(host_hostkeys, host, user_hostfile); load_hostkeys(host_hostkeys, host, system_hostfile); ip_hostkeys = NULL; if (!want_cert && options.check_host_ip) { ip_hostkeys = init_hostkeys(); load_hostkeys(ip_hostkeys, ip, user_hostfile); load_hostkeys(ip_hostkeys, ip, system_hostfile); } retry: /* Reload these as they may have changed on cert->key downgrade */ want_cert = key_is_cert(host_key); type = key_type(host_key); /* * Check if the host key is present in the user's list of known * hosts or in the systemwide list. */ host_status = check_key_in_hostkeys(host_hostkeys, host_key, &host_found); /* * Also perform check for the ip address, skip the check if we are * localhost, looking for a certificate, or the hostname was an ip * address to begin with. */ if (!want_cert && ip_hostkeys != NULL) { ip_status = check_key_in_hostkeys(ip_hostkeys, host_key, &ip_found); if (host_status == HOST_CHANGED && (ip_status != HOST_CHANGED || (ip_found != NULL && !key_equal(ip_found->key, host_found->key)))) host_ip_differ = 1; } else ip_status = host_status; switch (host_status) { case HOST_OK: /* The host is known and the key matches. */ debug("Host '%.200s' is known and matches the %s host %s.", host, type, want_cert ? "certificate" : "key"); debug("Found %s in %s:%lu", want_cert ? "CA key" : "key", host_found->file, host_found->line); if (want_cert && !check_host_cert(hostname, host_key)) goto fail; if (options.check_host_ip && ip_status == HOST_NEW) { if (readonly || want_cert) logit("%s host key for IP address " "'%.128s' not in list of known hosts.", type, ip); else if (!add_host_to_hostfile(user_hostfile, ip, host_key, options.hash_known_hosts)) logit("Failed to add the %s host key for IP " "address '%.128s' to the list of known " "hosts (%.30s).", type, ip, user_hostfile); else logit("Warning: Permanently added the %s host " "key for IP address '%.128s' to the list " "of known hosts.", type, ip); } else if (options.visual_host_key) { fp = key_fingerprint(host_key, SSH_FP_MD5, SSH_FP_HEX); ra = key_fingerprint(host_key, SSH_FP_MD5, SSH_FP_RANDOMART); logit("Host key fingerprint is %s\n%s\n", fp, ra); xfree(ra); xfree(fp); } break; case HOST_NEW: if (options.host_key_alias == NULL && port != 0 && port != SSH_DEFAULT_PORT) { debug("checking without port identifier"); if (check_host_key(hostname, hostaddr, 0, host_key, ROQUIET, user_hostfile, system_hostfile) == 0) { debug("found matching key w/out port"); break; } } if (readonly || want_cert) goto fail; /* The host is new. */ if (options.strict_host_key_checking == 1) { /* * User has requested strict host key checking. We * will not add the host key automatically. The only * alternative left is to abort. */ error("No %s host key is known for %.200s and you " "have requested strict checking.", type, host); goto fail; } else if (options.strict_host_key_checking == 2) { char msg1[1024], msg2[1024]; if (show_other_keys(host_hostkeys, host_key)) snprintf(msg1, sizeof(msg1), "\nbut keys of different type are already" " known for this host."); else snprintf(msg1, sizeof(msg1), "."); /* The default */ fp = key_fingerprint(host_key, SSH_FP_MD5, SSH_FP_HEX); ra = key_fingerprint(host_key, SSH_FP_MD5, SSH_FP_RANDOMART); msg2[0] = '\0'; if (options.verify_host_key_dns) { if (matching_host_key_dns) snprintf(msg2, sizeof(msg2), "Matching host key fingerprint" " found in DNS.\n"); else snprintf(msg2, sizeof(msg2), "No matching host key fingerprint" " found in DNS.\n"); } snprintf(msg, sizeof(msg), "The authenticity of host '%.200s (%s)' can't be " "established%s\n" "%s key fingerprint is %s.%s%s\n%s" "Are you sure you want to continue connecting " "(yes/no)? ", host, ip, msg1, type, fp, options.visual_host_key ? "\n" : "", options.visual_host_key ? ra : "", msg2); xfree(ra); xfree(fp); if (!confirm(msg)) goto fail; } /* * If not in strict mode, add the key automatically to the * local known_hosts file. */ if (options.check_host_ip && ip_status == HOST_NEW) { snprintf(hostline, sizeof(hostline), "%s,%s", host, ip); hostp = hostline; if (options.hash_known_hosts) { /* Add hash of host and IP separately */ r = add_host_to_hostfile(user_hostfile, host, host_key, options.hash_known_hosts) && add_host_to_hostfile(user_hostfile, ip, host_key, options.hash_known_hosts); } else { /* Add unhashed "host,ip" */ r = add_host_to_hostfile(user_hostfile, hostline, host_key, options.hash_known_hosts); } } else { r = add_host_to_hostfile(user_hostfile, host, host_key, options.hash_known_hosts); hostp = host; } if (!r) logit("Failed to add the host to the list of known " "hosts (%.500s).", user_hostfile); else logit("Warning: Permanently added '%.200s' (%s) to the " "list of known hosts.", hostp, type); break; case HOST_REVOKED: error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); error("@ WARNING: REVOKED HOST KEY DETECTED! @"); error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); error("The %s host key for %s is marked as revoked.", type, host); error("This could mean that a stolen key is being used to"); error("impersonate this host."); /* * If strict host key checking is in use, the user will have * to edit the key manually and we can only abort. */ if (options.strict_host_key_checking) { error("%s host key for %.200s was revoked and you have " "requested strict checking.", type, host); goto fail; } goto continue_unsafe; case HOST_CHANGED: if (want_cert) { /* * This is only a debug() since it is valid to have * CAs with wildcard DNS matches that don't match * all hosts that one might visit. */ debug("Host certificate authority does not " "match %s in %s:%lu", CA_MARKER, host_found->file, host_found->line); goto fail; } if (readonly == ROQUIET) goto fail; if (options.check_host_ip && host_ip_differ) { char *key_msg; if (ip_status == HOST_NEW) key_msg = "is unknown"; else if (ip_status == HOST_OK) key_msg = "is unchanged"; else key_msg = "has a different value"; error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); error("@ WARNING: POSSIBLE DNS SPOOFING DETECTED! @"); error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); error("The %s host key for %s has changed,", type, host); error("and the key for the corresponding IP address %s", ip); error("%s. This could either mean that", key_msg); error("DNS SPOOFING is happening or the IP address for the host"); error("and its host key have changed at the same time."); if (ip_status != HOST_NEW) error("Offending key for IP in %s:%lu", ip_found->file, ip_found->line); } /* The host key has changed. */ warn_changed_key(host_key); error("Add correct host key in %.100s to get rid of this message.", user_hostfile); error("Offending %s key in %s:%lu", key_type(host_found->key), host_found->file, host_found->line); /* * If strict host key checking is in use, the user will have * to edit the key manually and we can only abort. */ if (options.strict_host_key_checking) { error("%s host key for %.200s has changed and you have " "requested strict checking.", type, host); goto fail; } continue_unsafe: /* * If strict host key checking has not been requested, allow * the connection but without MITM-able authentication or * forwarding. */ if (options.password_authentication) { error("Password authentication is disabled to avoid " "man-in-the-middle attacks."); options.password_authentication = 0; cancelled_forwarding = 1; } if (options.kbd_interactive_authentication) { error("Keyboard-interactive authentication is disabled" " to avoid man-in-the-middle attacks."); options.kbd_interactive_authentication = 0; options.challenge_response_authentication = 0; cancelled_forwarding = 1; } if (options.challenge_response_authentication) { error("Challenge/response authentication is disabled" " to avoid man-in-the-middle attacks."); options.challenge_response_authentication = 0; cancelled_forwarding = 1; } if (options.forward_agent) { error("Agent forwarding is disabled to avoid " "man-in-the-middle attacks."); options.forward_agent = 0; cancelled_forwarding = 1; } if (options.forward_x11) { error("X11 forwarding is disabled to avoid " "man-in-the-middle attacks."); options.forward_x11 = 0; cancelled_forwarding = 1; } if (options.num_local_forwards > 0 || options.num_remote_forwards > 0) { error("Port forwarding is disabled to avoid " "man-in-the-middle attacks."); options.num_local_forwards = options.num_remote_forwards = 0; cancelled_forwarding = 1; } if (options.tun_open != SSH_TUNMODE_NO) { error("Tunnel forwarding is disabled to avoid " "man-in-the-middle attacks."); options.tun_open = SSH_TUNMODE_NO; cancelled_forwarding = 1; } if (options.exit_on_forward_failure && cancelled_forwarding) fatal("Error: forwarding disabled due to host key " "check failure"); /* * XXX Should permit the user to change to use the new id. * This could be done by converting the host key to an * identifying sentence, tell that the host identifies itself * by that sentence, and ask the user if he/she wishes to * accept the authentication. */ break; case HOST_FOUND: fatal("internal error"); break; } if (options.check_host_ip && host_status != HOST_CHANGED && ip_status == HOST_CHANGED) { snprintf(msg, sizeof(msg), "Warning: the %s host key for '%.200s' " "differs from the key for the IP address '%.128s'" "\nOffending key for IP in %s:%lu", type, host, ip, ip_found->file, ip_found->line); if (host_status == HOST_OK) { len = strlen(msg); snprintf(msg + len, sizeof(msg) - len, "\nMatching host key in %s:%lu", host_found->file, host_found->line); } if (options.strict_host_key_checking == 1) { logit("%s", msg); error("Exiting, you have requested strict checking."); goto fail; } else if (options.strict_host_key_checking == 2) { strlcat(msg, "\nAre you sure you want " "to continue connecting (yes/no)? ", sizeof(msg)); if (!confirm(msg)) goto fail; } else { logit("%s", msg); } } xfree(ip); xfree(host); if (host_hostkeys != NULL) free_hostkeys(host_hostkeys); if (ip_hostkeys != NULL) free_hostkeys(ip_hostkeys); return 0; fail: if (want_cert && host_status != HOST_REVOKED) { /* * No matching certificate. Downgrade cert to raw key and * search normally. */ debug("No matching CA found. Retry with plain key"); raw_key = key_from_private(host_key); if (key_drop_cert(raw_key) != 0) fatal("Couldn't drop certificate"); host_key = raw_key; goto retry; } if (raw_key != NULL) key_free(raw_key); xfree(ip); xfree(host); if (host_hostkeys != NULL) free_hostkeys(host_hostkeys); if (ip_hostkeys != NULL) free_hostkeys(ip_hostkeys); return -1; } /* returns 0 if key verifies or -1 if key does NOT verify */ int verify_host_key(char *host, struct sockaddr *hostaddr, Key *host_key) { struct stat st; int flags = 0; char *fp; fp = key_fingerprint(host_key, SSH_FP_MD5, SSH_FP_HEX); debug("Server host key: %s %s", key_type(host_key), fp); xfree(fp); /* XXX certs are not yet supported for DNS */ if (!key_is_cert(host_key) && options.verify_host_key_dns && verify_host_key_dns(host, hostaddr, host_key, &flags) == 0) { if (flags & DNS_VERIFY_FOUND) { if (options.verify_host_key_dns == 1 && flags & DNS_VERIFY_MATCH && flags & DNS_VERIFY_SECURE) return 0; if (flags & DNS_VERIFY_MATCH) { matching_host_key_dns = 1; } else { warn_changed_key(host_key); error("Update the SSHFP RR in DNS with the new " "host key to get rid of this message."); } } } /* return ok if the key can be found in an old keyfile */ if (stat(options.system_hostfile2, &st) == 0 || stat(options.user_hostfile2, &st) == 0) { if (check_host_key(host, hostaddr, options.port, host_key, RDONLY, options.user_hostfile2, options.system_hostfile2) == 0) return 0; } return check_host_key(host, hostaddr, options.port, host_key, RDRW, options.user_hostfile, options.system_hostfile); } /* * Starts a dialog with the server, and authenticates the current user on the * server. This does not need any extra privileges. The basic connection * to the server must already have been established before this is called. * If login fails, this function prints an error and never returns. * This function does not require super-user privileges. */ void ssh_login(Sensitive *sensitive, const char *orighost, struct sockaddr *hostaddr, u_short port, struct passwd *pw, int timeout_ms) { char *host, *cp; char *server_user, *local_user; local_user = xstrdup(pw->pw_name); server_user = options.user ? options.user : local_user; /* Convert the user-supplied hostname into all lowercase. */ host = xstrdup(orighost); for (cp = host; *cp; cp++) if (isupper(*cp)) *cp = (char)tolower(*cp); /* Exchange protocol version identification strings with the server. */ ssh_exchange_identification(timeout_ms); /* Put the connection into non-blocking mode. */ packet_set_nonblocking(); /* key exchange */ /* authenticate user */ if (compat20) { ssh_kex2(host, hostaddr, port); ssh_userauth2(local_user, server_user, host, sensitive); } else { ssh_kex(host, hostaddr); ssh_userauth1(local_user, server_user, host, sensitive); } xfree(local_user); } void ssh_put_password(char *password) { int size; char *padded; if (datafellows & SSH_BUG_PASSWORDPAD) { packet_put_cstring(password); return; } size = roundup(strlen(password) + 1, 32); padded = xcalloc(1, size); strlcpy(padded, password, size); packet_put_string(padded, size); memset(padded, 0, size); xfree(padded); } /* print all known host keys for a given host, but skip keys of given type */ static int show_other_keys(struct hostkeys *hostkeys, Key *key) { int type[] = { KEY_RSA1, KEY_RSA, KEY_DSA, KEY_ECDSA, -1}; int i, ret = 0; char *fp, *ra; const struct hostkey_entry *found; for (i = 0; type[i] != -1; i++) { if (type[i] == key->type) continue; if (!lookup_key_in_hostkeys_by_type(hostkeys, type[i], &found)) continue; fp = key_fingerprint(found->key, SSH_FP_MD5, SSH_FP_HEX); ra = key_fingerprint(found->key, SSH_FP_MD5, SSH_FP_RANDOMART); logit("WARNING: %s key found for host %s\n" "in %s:%lu\n" "%s key fingerprint %s.", key_type(found->key), found->host, found->file, found->line, key_type(found->key), fp); if (options.visual_host_key) logit("%s", ra); xfree(ra); xfree(fp); ret = 1; } return ret; } static void warn_changed_key(Key *host_key) { char *fp; fp = key_fingerprint(host_key, SSH_FP_MD5, SSH_FP_HEX); error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); error("@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @"); error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); error("IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!"); error("Someone could be eavesdropping on you right now (man-in-the-middle attack)!"); error("It is also possible that a host key has just been changed."); error("The fingerprint for the %s key sent by the remote host is\n%s.", key_type(host_key), fp); error("Please contact your system administrator."); xfree(fp); } /* * Execute a local command */ int ssh_local_cmd(const char *args) { char *shell; pid_t pid; int status; void (*osighand)(int); if (!options.permit_local_command || args == NULL || !*args) return (1); if ((shell = getenv("SHELL")) == NULL || *shell == '\0') shell = _PATH_BSHELL; osighand = signal(SIGCHLD, SIG_DFL); pid = fork(); if (pid == 0) { signal(SIGPIPE, SIG_DFL); debug3("Executing %s -c \"%s\"", shell, args); execl(shell, shell, "-c", args, (char *)NULL); error("Couldn't execute %s -c \"%s\": %s", shell, args, strerror(errno)); _exit(1); } else if (pid == -1) fatal("fork failed: %.100s", strerror(errno)); while (waitpid(pid, &status, 0) == -1) if (errno != EINTR) fatal("Couldn't wait for child: %s", strerror(errno)); signal(SIGCHLD, osighand); if (!WIFEXITED(status)) return (1); return (WEXITSTATUS(status)); } diff --git a/crypto/openssh/sshconnect2.c b/crypto/openssh/sshconnect2.c index 3cb9b101cb88..7352276fd95f 100644 --- a/crypto/openssh/sshconnect2.c +++ b/crypto/openssh/sshconnect2.c @@ -1,1916 +1,1950 @@ /* $OpenBSD: sshconnect2.c,v 1.186 2010/11/29 23:45:51 djm Exp $ */ +/* $FreeBSD$ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. * Copyright (c) 2008 Damien Miller. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "includes.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(HAVE_STRNVIS) && defined(HAVE_VIS_H) #include #endif #include "openbsd-compat/sys-queue.h" #include "xmalloc.h" #include "ssh.h" #include "ssh2.h" #include "buffer.h" #include "packet.h" #include "compat.h" #include "cipher.h" #include "key.h" #include "kex.h" #include "myproposal.h" #include "sshconnect.h" #include "authfile.h" #include "dh.h" #include "authfd.h" #include "log.h" #include "readconf.h" #include "misc.h" #include "match.h" #include "dispatch.h" #include "canohost.h" #include "msg.h" #include "pathnames.h" #include "uidswap.h" #include "hostfile.h" #include "schnorr.h" #include "jpake.h" #ifdef GSSAPI #include "ssh-gss.h" #endif /* import */ extern char *client_version_string; extern char *server_version_string; extern Options options; +#ifdef NONE_CIPHER_ENABLED +extern Kex *xxx_kex; + +/* + * tty_flag is set in ssh.c so we can use it here. If set then prevent + * the switch to the null cipher. + */ + +extern int tty_flag; +#endif /* * SSH2 key exchange */ u_char *session_id2 = NULL; u_int session_id2_len = 0; char *xxx_host; struct sockaddr *xxx_hostaddr; Kex *xxx_kex = NULL; static int verify_host_key_callback(Key *hostkey) { if (verify_host_key(xxx_host, xxx_hostaddr, hostkey) == -1) fatal("Host key verification failed."); return 0; } static char * order_hostkeyalgs(char *host, struct sockaddr *hostaddr, u_short port) { char *oavail, *avail, *first, *last, *alg, *hostname, *ret; size_t maxlen; struct hostkeys *hostkeys; int ktype; /* Find all hostkeys for this hostname */ get_hostfile_hostname_ipaddr(host, hostaddr, port, &hostname, NULL); hostkeys = init_hostkeys(); load_hostkeys(hostkeys, hostname, options.user_hostfile2); load_hostkeys(hostkeys, hostname, options.system_hostfile2); load_hostkeys(hostkeys, hostname, options.user_hostfile); load_hostkeys(hostkeys, hostname, options.system_hostfile); oavail = avail = xstrdup(KEX_DEFAULT_PK_ALG); maxlen = strlen(avail) + 1; first = xmalloc(maxlen); last = xmalloc(maxlen); *first = *last = '\0'; #define ALG_APPEND(to, from) \ do { \ if (*to != '\0') \ strlcat(to, ",", maxlen); \ strlcat(to, from, maxlen); \ } while (0) while ((alg = strsep(&avail, ",")) && *alg != '\0') { if ((ktype = key_type_from_name(alg)) == KEY_UNSPEC) fatal("%s: unknown alg %s", __func__, alg); if (lookup_key_in_hostkeys_by_type(hostkeys, key_type_plain(ktype), NULL)) ALG_APPEND(first, alg); else ALG_APPEND(last, alg); } #undef ALG_APPEND xasprintf(&ret, "%s%s%s", first, *first == '\0' ? "" : ",", last); if (*first != '\0') debug3("%s: prefer hostkeyalgs: %s", __func__, first); xfree(first); xfree(last); xfree(hostname); xfree(oavail); free_hostkeys(hostkeys); return ret; } void ssh_kex2(char *host, struct sockaddr *hostaddr, u_short port) { Kex *kex; xxx_host = host; xxx_hostaddr = hostaddr; if (options.ciphers == (char *)-1) { logit("No valid ciphers for protocol version 2 given, using defaults."); options.ciphers = NULL; } if (options.ciphers != NULL) { myproposal[PROPOSAL_ENC_ALGS_CTOS] = myproposal[PROPOSAL_ENC_ALGS_STOC] = options.ciphers; } myproposal[PROPOSAL_ENC_ALGS_CTOS] = compat_cipher_proposal(myproposal[PROPOSAL_ENC_ALGS_CTOS]); myproposal[PROPOSAL_ENC_ALGS_STOC] = compat_cipher_proposal(myproposal[PROPOSAL_ENC_ALGS_STOC]); if (options.compression) { myproposal[PROPOSAL_COMP_ALGS_CTOS] = myproposal[PROPOSAL_COMP_ALGS_STOC] = "zlib@openssh.com,zlib,none"; } else { myproposal[PROPOSAL_COMP_ALGS_CTOS] = myproposal[PROPOSAL_COMP_ALGS_STOC] = "none,zlib@openssh.com,zlib"; } if (options.macs != NULL) { myproposal[PROPOSAL_MAC_ALGS_CTOS] = myproposal[PROPOSAL_MAC_ALGS_STOC] = options.macs; } if (options.hostkeyalgorithms != NULL) myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = options.hostkeyalgorithms; else { /* Prefer algorithms that we already have keys for */ myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = order_hostkeyalgs(host, hostaddr, port); } if (options.kex_algorithms != NULL) myproposal[PROPOSAL_KEX_ALGS] = options.kex_algorithms; if (options.rekey_limit) packet_set_rekey_limit((u_int32_t)options.rekey_limit); /* start key exchange */ kex = kex_setup(myproposal); kex->kex[KEX_DH_GRP1_SHA1] = kexdh_client; kex->kex[KEX_DH_GRP14_SHA1] = kexdh_client; kex->kex[KEX_DH_GEX_SHA1] = kexgex_client; kex->kex[KEX_DH_GEX_SHA256] = kexgex_client; kex->kex[KEX_ECDH_SHA2] = kexecdh_client; kex->client_version_string=client_version_string; kex->server_version_string=server_version_string; kex->verify_host_key=&verify_host_key_callback; xxx_kex = kex; dispatch_run(DISPATCH_BLOCK, &kex->done, kex); if (options.use_roaming && !kex->roaming) { debug("Roaming not allowed by server"); options.use_roaming = 0; } session_id2 = kex->session_id; session_id2_len = kex->session_id_len; #ifdef DEBUG_KEXDH /* send 1st encrypted/maced/compressed message */ packet_start(SSH2_MSG_IGNORE); packet_put_cstring("markus"); packet_send(); packet_write_wait(); #endif } /* * Authenticate user */ typedef struct Authctxt Authctxt; typedef struct Authmethod Authmethod; typedef struct identity Identity; typedef struct idlist Idlist; struct identity { TAILQ_ENTRY(identity) next; AuthenticationConnection *ac; /* set if agent supports key */ Key *key; /* public/private key */ char *filename; /* comment for agent-only keys */ int tried; int isprivate; /* key points to the private key */ }; TAILQ_HEAD(idlist, identity); struct Authctxt { const char *server_user; const char *local_user; const char *host; const char *service; Authmethod *method; sig_atomic_t success; char *authlist; /* pubkey */ Idlist keys; AuthenticationConnection *agent; /* hostbased */ Sensitive *sensitive; /* kbd-interactive */ int info_req_seen; /* generic */ void *methoddata; }; struct Authmethod { char *name; /* string to compare against server's list */ int (*userauth)(Authctxt *authctxt); void (*cleanup)(Authctxt *authctxt); int *enabled; /* flag in option struct that enables method */ int *batch_flag; /* flag in option struct that disables method */ }; void input_userauth_success(int, u_int32_t, void *); void input_userauth_success_unexpected(int, u_int32_t, void *); void input_userauth_failure(int, u_int32_t, void *); void input_userauth_banner(int, u_int32_t, void *); void input_userauth_error(int, u_int32_t, void *); void input_userauth_info_req(int, u_int32_t, void *); void input_userauth_pk_ok(int, u_int32_t, void *); void input_userauth_passwd_changereq(int, u_int32_t, void *); void input_userauth_jpake_server_step1(int, u_int32_t, void *); void input_userauth_jpake_server_step2(int, u_int32_t, void *); void input_userauth_jpake_server_confirm(int, u_int32_t, void *); int userauth_none(Authctxt *); int userauth_pubkey(Authctxt *); int userauth_passwd(Authctxt *); int userauth_kbdint(Authctxt *); int userauth_hostbased(Authctxt *); int userauth_jpake(Authctxt *); void userauth_jpake_cleanup(Authctxt *); #ifdef GSSAPI int userauth_gssapi(Authctxt *authctxt); void input_gssapi_response(int type, u_int32_t, void *); void input_gssapi_token(int type, u_int32_t, void *); void input_gssapi_hash(int type, u_int32_t, void *); void input_gssapi_error(int, u_int32_t, void *); void input_gssapi_errtok(int, u_int32_t, void *); #endif void userauth(Authctxt *, char *); static int sign_and_send_pubkey(Authctxt *, Identity *); static void pubkey_prepare(Authctxt *); static void pubkey_cleanup(Authctxt *); static Key *load_identity_file(char *); static Authmethod *authmethod_get(char *authlist); static Authmethod *authmethod_lookup(const char *name); static char *authmethods_get(void); Authmethod authmethods[] = { #ifdef GSSAPI {"gssapi-with-mic", userauth_gssapi, NULL, &options.gss_authentication, NULL}, #endif {"hostbased", userauth_hostbased, NULL, &options.hostbased_authentication, NULL}, {"publickey", userauth_pubkey, NULL, &options.pubkey_authentication, NULL}, #ifdef JPAKE {"jpake-01@openssh.com", userauth_jpake, userauth_jpake_cleanup, &options.zero_knowledge_password_authentication, &options.batch_mode}, #endif {"keyboard-interactive", userauth_kbdint, NULL, &options.kbd_interactive_authentication, &options.batch_mode}, {"password", userauth_passwd, NULL, &options.password_authentication, &options.batch_mode}, {"none", userauth_none, NULL, NULL, NULL}, {NULL, NULL, NULL, NULL, NULL} }; void ssh_userauth2(const char *local_user, const char *server_user, char *host, Sensitive *sensitive) { Authctxt authctxt; int type; if (options.challenge_response_authentication) options.kbd_interactive_authentication = 1; packet_start(SSH2_MSG_SERVICE_REQUEST); packet_put_cstring("ssh-userauth"); packet_send(); debug("SSH2_MSG_SERVICE_REQUEST sent"); packet_write_wait(); type = packet_read(); if (type != SSH2_MSG_SERVICE_ACCEPT) fatal("Server denied authentication request: %d", type); if (packet_remaining() > 0) { char *reply = packet_get_string(NULL); debug2("service_accept: %s", reply); xfree(reply); } else { debug2("buggy server: service_accept w/o service"); } packet_check_eom(); debug("SSH2_MSG_SERVICE_ACCEPT received"); if (options.preferred_authentications == NULL) options.preferred_authentications = authmethods_get(); /* setup authentication context */ memset(&authctxt, 0, sizeof(authctxt)); pubkey_prepare(&authctxt); authctxt.server_user = server_user; authctxt.local_user = local_user; authctxt.host = host; authctxt.service = "ssh-connection"; /* service name */ authctxt.success = 0; authctxt.method = authmethod_lookup("none"); authctxt.authlist = NULL; authctxt.methoddata = NULL; authctxt.sensitive = sensitive; authctxt.info_req_seen = 0; if (authctxt.method == NULL) fatal("ssh_userauth2: internal error: cannot send userauth none request"); /* initial userauth request */ userauth_none(&authctxt); dispatch_init(&input_userauth_error); dispatch_set(SSH2_MSG_USERAUTH_SUCCESS, &input_userauth_success); dispatch_set(SSH2_MSG_USERAUTH_FAILURE, &input_userauth_failure); dispatch_set(SSH2_MSG_USERAUTH_BANNER, &input_userauth_banner); dispatch_run(DISPATCH_BLOCK, &authctxt.success, &authctxt); /* loop until success */ pubkey_cleanup(&authctxt); dispatch_range(SSH2_MSG_USERAUTH_MIN, SSH2_MSG_USERAUTH_MAX, NULL); +#ifdef NONE_CIPHER_ENABLED + /* + * If the user explicitly requests to use the none cipher enable it + * post authentication and only if the right conditions are met: both + * of the NONE switches must be true and there must be no tty allocated. + */ + if (options.none_switch == 1 && options.none_enabled == 1) { + if (!tty_flag) { + debug("Requesting none cipher re-keying..."); + myproposal[PROPOSAL_ENC_ALGS_STOC] = "none"; + myproposal[PROPOSAL_ENC_ALGS_CTOS] = "none"; + kex_prop2buf(&xxx_kex->my, myproposal); + packet_request_rekeying(); + fprintf(stderr, "WARNING: enabled NONE cipher\n"); + } else { + /* Requested NONE cipher on an interactive session. */ + debug("Cannot switch to NONE cipher with tty " + "allocated"); + fprintf(stderr, "NONE cipher switch disabled given " + "a TTY is allocated\n"); + } + } +#endif debug("Authentication succeeded (%s).", authctxt.method->name); } void userauth(Authctxt *authctxt, char *authlist) { if (authctxt->method != NULL && authctxt->method->cleanup != NULL) authctxt->method->cleanup(authctxt); if (authctxt->methoddata) { xfree(authctxt->methoddata); authctxt->methoddata = NULL; } if (authlist == NULL) { authlist = authctxt->authlist; } else { if (authctxt->authlist) xfree(authctxt->authlist); authctxt->authlist = authlist; } for (;;) { Authmethod *method = authmethod_get(authlist); if (method == NULL) fatal("Permission denied (%s).", authlist); authctxt->method = method; /* reset the per method handler */ dispatch_range(SSH2_MSG_USERAUTH_PER_METHOD_MIN, SSH2_MSG_USERAUTH_PER_METHOD_MAX, NULL); /* and try new method */ if (method->userauth(authctxt) != 0) { debug2("we sent a %s packet, wait for reply", method->name); break; } else { debug2("we did not send a packet, disable method"); method->enabled = NULL; } } } /* ARGSUSED */ void input_userauth_error(int type, u_int32_t seq, void *ctxt) { fatal("input_userauth_error: bad message during authentication: " "type %d", type); } /* ARGSUSED */ void input_userauth_banner(int type, u_int32_t seq, void *ctxt) { char *msg, *raw, *lang; u_int len; debug3("input_userauth_banner"); raw = packet_get_string(&len); lang = packet_get_string(NULL); if (len > 0 && options.log_level >= SYSLOG_LEVEL_INFO) { if (len > 65536) len = 65536; msg = xmalloc(len * 4 + 1); /* max expansion from strnvis() */ strnvis(msg, raw, len * 4 + 1, VIS_SAFE|VIS_OCTAL|VIS_NOSLASH); fprintf(stderr, "%s", msg); xfree(msg); } xfree(raw); xfree(lang); } /* ARGSUSED */ void input_userauth_success(int type, u_int32_t seq, void *ctxt) { Authctxt *authctxt = ctxt; if (authctxt == NULL) fatal("input_userauth_success: no authentication context"); if (authctxt->authlist) { xfree(authctxt->authlist); authctxt->authlist = NULL; } if (authctxt->method != NULL && authctxt->method->cleanup != NULL) authctxt->method->cleanup(authctxt); if (authctxt->methoddata) { xfree(authctxt->methoddata); authctxt->methoddata = NULL; } authctxt->success = 1; /* break out */ } void input_userauth_success_unexpected(int type, u_int32_t seq, void *ctxt) { Authctxt *authctxt = ctxt; if (authctxt == NULL) fatal("%s: no authentication context", __func__); fatal("Unexpected authentication success during %s.", authctxt->method->name); } /* ARGSUSED */ void input_userauth_failure(int type, u_int32_t seq, void *ctxt) { Authctxt *authctxt = ctxt; char *authlist = NULL; int partial; if (authctxt == NULL) fatal("input_userauth_failure: no authentication context"); authlist = packet_get_string(NULL); partial = packet_get_char(); packet_check_eom(); if (partial != 0) logit("Authenticated with partial success."); debug("Authentications that can continue: %s", authlist); userauth(authctxt, authlist); } /* ARGSUSED */ void input_userauth_pk_ok(int type, u_int32_t seq, void *ctxt) { Authctxt *authctxt = ctxt; Key *key = NULL; Identity *id = NULL; Buffer b; int pktype, sent = 0; u_int alen, blen; char *pkalg, *fp; u_char *pkblob; if (authctxt == NULL) fatal("input_userauth_pk_ok: no authentication context"); if (datafellows & SSH_BUG_PKOK) { /* this is similar to SSH_BUG_PKAUTH */ debug2("input_userauth_pk_ok: SSH_BUG_PKOK"); pkblob = packet_get_string(&blen); buffer_init(&b); buffer_append(&b, pkblob, blen); pkalg = buffer_get_string(&b, &alen); buffer_free(&b); } else { pkalg = packet_get_string(&alen); pkblob = packet_get_string(&blen); } packet_check_eom(); debug("Server accepts key: pkalg %s blen %u", pkalg, blen); if ((pktype = key_type_from_name(pkalg)) == KEY_UNSPEC) { debug("unknown pkalg %s", pkalg); goto done; } if ((key = key_from_blob(pkblob, blen)) == NULL) { debug("no key from blob. pkalg %s", pkalg); goto done; } if (key->type != pktype) { error("input_userauth_pk_ok: type mismatch " "for decoded key (received %d, expected %d)", key->type, pktype); goto done; } fp = key_fingerprint(key, SSH_FP_MD5, SSH_FP_HEX); debug2("input_userauth_pk_ok: fp %s", fp); xfree(fp); /* * search keys in the reverse order, because last candidate has been * moved to the end of the queue. this also avoids confusion by * duplicate keys */ TAILQ_FOREACH_REVERSE(id, &authctxt->keys, idlist, next) { if (key_equal(key, id->key)) { sent = sign_and_send_pubkey(authctxt, id); break; } } done: if (key != NULL) key_free(key); xfree(pkalg); xfree(pkblob); /* try another method if we did not send a packet */ if (sent == 0) userauth(authctxt, NULL); } #ifdef GSSAPI int userauth_gssapi(Authctxt *authctxt) { Gssctxt *gssctxt = NULL; static gss_OID_set gss_supported = NULL; static u_int mech = 0; OM_uint32 min; int ok = 0; /* Try one GSSAPI method at a time, rather than sending them all at * once. */ if (gss_supported == NULL) gss_indicate_mechs(&min, &gss_supported); /* Check to see if the mechanism is usable before we offer it */ while (mech < gss_supported->count && !ok) { /* My DER encoding requires length<128 */ if (gss_supported->elements[mech].length < 128 && ssh_gssapi_check_mechanism(&gssctxt, &gss_supported->elements[mech], authctxt->host)) { ok = 1; /* Mechanism works */ } else { mech++; } } if (!ok) return 0; authctxt->methoddata=(void *)gssctxt; packet_start(SSH2_MSG_USERAUTH_REQUEST); packet_put_cstring(authctxt->server_user); packet_put_cstring(authctxt->service); packet_put_cstring(authctxt->method->name); packet_put_int(1); packet_put_int((gss_supported->elements[mech].length) + 2); packet_put_char(SSH_GSS_OIDTYPE); packet_put_char(gss_supported->elements[mech].length); packet_put_raw(gss_supported->elements[mech].elements, gss_supported->elements[mech].length); packet_send(); dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_RESPONSE, &input_gssapi_response); dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_TOKEN, &input_gssapi_token); dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_ERROR, &input_gssapi_error); dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_ERRTOK, &input_gssapi_errtok); mech++; /* Move along to next candidate */ return 1; } static OM_uint32 process_gssapi_token(void *ctxt, gss_buffer_t recv_tok) { Authctxt *authctxt = ctxt; Gssctxt *gssctxt = authctxt->methoddata; gss_buffer_desc send_tok = GSS_C_EMPTY_BUFFER; gss_buffer_desc mic = GSS_C_EMPTY_BUFFER; gss_buffer_desc gssbuf; OM_uint32 status, ms, flags; Buffer b; status = ssh_gssapi_init_ctx(gssctxt, options.gss_deleg_creds, recv_tok, &send_tok, &flags); if (send_tok.length > 0) { if (GSS_ERROR(status)) packet_start(SSH2_MSG_USERAUTH_GSSAPI_ERRTOK); else packet_start(SSH2_MSG_USERAUTH_GSSAPI_TOKEN); packet_put_string(send_tok.value, send_tok.length); packet_send(); gss_release_buffer(&ms, &send_tok); } if (status == GSS_S_COMPLETE) { /* send either complete or MIC, depending on mechanism */ if (!(flags & GSS_C_INTEG_FLAG)) { packet_start(SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE); packet_send(); } else { ssh_gssapi_buildmic(&b, authctxt->server_user, authctxt->service, "gssapi-with-mic"); gssbuf.value = buffer_ptr(&b); gssbuf.length = buffer_len(&b); status = ssh_gssapi_sign(gssctxt, &gssbuf, &mic); if (!GSS_ERROR(status)) { packet_start(SSH2_MSG_USERAUTH_GSSAPI_MIC); packet_put_string(mic.value, mic.length); packet_send(); } buffer_free(&b); gss_release_buffer(&ms, &mic); } } return status; } /* ARGSUSED */ void input_gssapi_response(int type, u_int32_t plen, void *ctxt) { Authctxt *authctxt = ctxt; Gssctxt *gssctxt; int oidlen; char *oidv; if (authctxt == NULL) fatal("input_gssapi_response: no authentication context"); gssctxt = authctxt->methoddata; /* Setup our OID */ oidv = packet_get_string(&oidlen); if (oidlen <= 2 || oidv[0] != SSH_GSS_OIDTYPE || oidv[1] != oidlen - 2) { xfree(oidv); debug("Badly encoded mechanism OID received"); userauth(authctxt, NULL); return; } if (!ssh_gssapi_check_oid(gssctxt, oidv + 2, oidlen - 2)) fatal("Server returned different OID than expected"); packet_check_eom(); xfree(oidv); if (GSS_ERROR(process_gssapi_token(ctxt, GSS_C_NO_BUFFER))) { /* Start again with next method on list */ debug("Trying to start again"); userauth(authctxt, NULL); return; } } /* ARGSUSED */ void input_gssapi_token(int type, u_int32_t plen, void *ctxt) { Authctxt *authctxt = ctxt; gss_buffer_desc recv_tok; OM_uint32 status; u_int slen; if (authctxt == NULL) fatal("input_gssapi_response: no authentication context"); recv_tok.value = packet_get_string(&slen); recv_tok.length = slen; /* safe typecast */ packet_check_eom(); status = process_gssapi_token(ctxt, &recv_tok); xfree(recv_tok.value); if (GSS_ERROR(status)) { /* Start again with the next method in the list */ userauth(authctxt, NULL); return; } } /* ARGSUSED */ void input_gssapi_errtok(int type, u_int32_t plen, void *ctxt) { Authctxt *authctxt = ctxt; Gssctxt *gssctxt; gss_buffer_desc send_tok = GSS_C_EMPTY_BUFFER; gss_buffer_desc recv_tok; OM_uint32 status, ms; u_int len; if (authctxt == NULL) fatal("input_gssapi_response: no authentication context"); gssctxt = authctxt->methoddata; recv_tok.value = packet_get_string(&len); recv_tok.length = len; packet_check_eom(); /* Stick it into GSSAPI and see what it says */ status = ssh_gssapi_init_ctx(gssctxt, options.gss_deleg_creds, &recv_tok, &send_tok, NULL); xfree(recv_tok.value); gss_release_buffer(&ms, &send_tok); /* Server will be returning a failed packet after this one */ } /* ARGSUSED */ void input_gssapi_error(int type, u_int32_t plen, void *ctxt) { OM_uint32 maj, min; char *msg; char *lang; maj=packet_get_int(); min=packet_get_int(); msg=packet_get_string(NULL); lang=packet_get_string(NULL); packet_check_eom(); debug("Server GSSAPI Error:\n%s", msg); xfree(msg); xfree(lang); } #endif /* GSSAPI */ int userauth_none(Authctxt *authctxt) { /* initial userauth request */ packet_start(SSH2_MSG_USERAUTH_REQUEST); packet_put_cstring(authctxt->server_user); packet_put_cstring(authctxt->service); packet_put_cstring(authctxt->method->name); packet_send(); return 1; } int userauth_passwd(Authctxt *authctxt) { static int attempt = 0; char prompt[150]; char *password; const char *host = options.host_key_alias ? options.host_key_alias : authctxt->host; if (attempt++ >= options.number_of_password_prompts) return 0; if (attempt != 1) error("Permission denied, please try again."); snprintf(prompt, sizeof(prompt), "%.30s@%.128s's password: ", authctxt->server_user, host); password = read_passphrase(prompt, 0); packet_start(SSH2_MSG_USERAUTH_REQUEST); packet_put_cstring(authctxt->server_user); packet_put_cstring(authctxt->service); packet_put_cstring(authctxt->method->name); packet_put_char(0); packet_put_cstring(password); memset(password, 0, strlen(password)); xfree(password); packet_add_padding(64); packet_send(); dispatch_set(SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ, &input_userauth_passwd_changereq); return 1; } /* * parse PASSWD_CHANGEREQ, prompt user and send SSH2_MSG_USERAUTH_REQUEST */ /* ARGSUSED */ void input_userauth_passwd_changereq(int type, u_int32_t seqnr, void *ctxt) { Authctxt *authctxt = ctxt; char *info, *lang, *password = NULL, *retype = NULL; char prompt[150]; const char *host = options.host_key_alias ? options.host_key_alias : authctxt->host; debug2("input_userauth_passwd_changereq"); if (authctxt == NULL) fatal("input_userauth_passwd_changereq: " "no authentication context"); info = packet_get_string(NULL); lang = packet_get_string(NULL); if (strlen(info) > 0) logit("%s", info); xfree(info); xfree(lang); packet_start(SSH2_MSG_USERAUTH_REQUEST); packet_put_cstring(authctxt->server_user); packet_put_cstring(authctxt->service); packet_put_cstring(authctxt->method->name); packet_put_char(1); /* additional info */ snprintf(prompt, sizeof(prompt), "Enter %.30s@%.128s's old password: ", authctxt->server_user, host); password = read_passphrase(prompt, 0); packet_put_cstring(password); memset(password, 0, strlen(password)); xfree(password); password = NULL; while (password == NULL) { snprintf(prompt, sizeof(prompt), "Enter %.30s@%.128s's new password: ", authctxt->server_user, host); password = read_passphrase(prompt, RP_ALLOW_EOF); if (password == NULL) { /* bail out */ return; } snprintf(prompt, sizeof(prompt), "Retype %.30s@%.128s's new password: ", authctxt->server_user, host); retype = read_passphrase(prompt, 0); if (strcmp(password, retype) != 0) { memset(password, 0, strlen(password)); xfree(password); logit("Mismatch; try again, EOF to quit."); password = NULL; } memset(retype, 0, strlen(retype)); xfree(retype); } packet_put_cstring(password); memset(password, 0, strlen(password)); xfree(password); packet_add_padding(64); packet_send(); dispatch_set(SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ, &input_userauth_passwd_changereq); } #ifdef JPAKE static char * pw_encrypt(const char *password, const char *crypt_scheme, const char *salt) { /* OpenBSD crypt(3) handles all of these */ if (strcmp(crypt_scheme, "crypt") == 0 || strcmp(crypt_scheme, "bcrypt") == 0 || strcmp(crypt_scheme, "md5crypt") == 0 || strcmp(crypt_scheme, "crypt-extended") == 0) return xstrdup(crypt(password, salt)); error("%s: unsupported password encryption scheme \"%.100s\"", __func__, crypt_scheme); return NULL; } static BIGNUM * jpake_password_to_secret(Authctxt *authctxt, const char *crypt_scheme, const char *salt) { char prompt[256], *password, *crypted; u_char *secret; u_int secret_len; BIGNUM *ret; snprintf(prompt, sizeof(prompt), "%.30s@%.128s's password (JPAKE): ", authctxt->server_user, authctxt->host); password = read_passphrase(prompt, 0); if ((crypted = pw_encrypt(password, crypt_scheme, salt)) == NULL) { logit("Disabling %s authentication", authctxt->method->name); authctxt->method->enabled = NULL; /* Continue with an empty password to fail gracefully */ crypted = xstrdup(""); } #ifdef JPAKE_DEBUG debug3("%s: salt = %s", __func__, salt); debug3("%s: scheme = %s", __func__, crypt_scheme); debug3("%s: crypted = %s", __func__, crypted); #endif if (hash_buffer(crypted, strlen(crypted), EVP_sha256(), &secret, &secret_len) != 0) fatal("%s: hash_buffer", __func__); bzero(password, strlen(password)); bzero(crypted, strlen(crypted)); xfree(password); xfree(crypted); if ((ret = BN_bin2bn(secret, secret_len, NULL)) == NULL) fatal("%s: BN_bin2bn (secret)", __func__); bzero(secret, secret_len); xfree(secret); return ret; } /* ARGSUSED */ void input_userauth_jpake_server_step1(int type, u_int32_t seq, void *ctxt) { Authctxt *authctxt = ctxt; struct jpake_ctx *pctx = authctxt->methoddata; u_char *x3_proof, *x4_proof, *x2_s_proof; u_int x3_proof_len, x4_proof_len, x2_s_proof_len; char *crypt_scheme, *salt; /* Disable this message */ dispatch_set(SSH2_MSG_USERAUTH_JPAKE_SERVER_STEP1, NULL); if ((pctx->g_x3 = BN_new()) == NULL || (pctx->g_x4 = BN_new()) == NULL) fatal("%s: BN_new", __func__); /* Fetch step 1 values */ crypt_scheme = packet_get_string(NULL); salt = packet_get_string(NULL); pctx->server_id = packet_get_string(&pctx->server_id_len); packet_get_bignum2(pctx->g_x3); packet_get_bignum2(pctx->g_x4); x3_proof = packet_get_string(&x3_proof_len); x4_proof = packet_get_string(&x4_proof_len); packet_check_eom(); JPAKE_DEBUG_CTX((pctx, "step 1 received in %s", __func__)); /* Obtain password and derive secret */ pctx->s = jpake_password_to_secret(authctxt, crypt_scheme, salt); bzero(crypt_scheme, strlen(crypt_scheme)); bzero(salt, strlen(salt)); xfree(crypt_scheme); xfree(salt); JPAKE_DEBUG_BN((pctx->s, "%s: s = ", __func__)); /* Calculate step 2 values */ jpake_step2(pctx->grp, pctx->s, pctx->g_x1, pctx->g_x3, pctx->g_x4, pctx->x2, pctx->server_id, pctx->server_id_len, pctx->client_id, pctx->client_id_len, x3_proof, x3_proof_len, x4_proof, x4_proof_len, &pctx->a, &x2_s_proof, &x2_s_proof_len); bzero(x3_proof, x3_proof_len); bzero(x4_proof, x4_proof_len); xfree(x3_proof); xfree(x4_proof); JPAKE_DEBUG_CTX((pctx, "step 2 sending in %s", __func__)); /* Send values for step 2 */ packet_start(SSH2_MSG_USERAUTH_JPAKE_CLIENT_STEP2); packet_put_bignum2(pctx->a); packet_put_string(x2_s_proof, x2_s_proof_len); packet_send(); bzero(x2_s_proof, x2_s_proof_len); xfree(x2_s_proof); /* Expect step 2 packet from peer */ dispatch_set(SSH2_MSG_USERAUTH_JPAKE_SERVER_STEP2, input_userauth_jpake_server_step2); } /* ARGSUSED */ void input_userauth_jpake_server_step2(int type, u_int32_t seq, void *ctxt) { Authctxt *authctxt = ctxt; struct jpake_ctx *pctx = authctxt->methoddata; u_char *x4_s_proof; u_int x4_s_proof_len; /* Disable this message */ dispatch_set(SSH2_MSG_USERAUTH_JPAKE_SERVER_STEP2, NULL); if ((pctx->b = BN_new()) == NULL) fatal("%s: BN_new", __func__); /* Fetch step 2 values */ packet_get_bignum2(pctx->b); x4_s_proof = packet_get_string(&x4_s_proof_len); packet_check_eom(); JPAKE_DEBUG_CTX((pctx, "step 2 received in %s", __func__)); /* Derive shared key and calculate confirmation hash */ jpake_key_confirm(pctx->grp, pctx->s, pctx->b, pctx->x2, pctx->g_x1, pctx->g_x2, pctx->g_x3, pctx->g_x4, pctx->client_id, pctx->client_id_len, pctx->server_id, pctx->server_id_len, session_id2, session_id2_len, x4_s_proof, x4_s_proof_len, &pctx->k, &pctx->h_k_cid_sessid, &pctx->h_k_cid_sessid_len); bzero(x4_s_proof, x4_s_proof_len); xfree(x4_s_proof); JPAKE_DEBUG_CTX((pctx, "confirm sending in %s", __func__)); /* Send key confirmation proof */ packet_start(SSH2_MSG_USERAUTH_JPAKE_CLIENT_CONFIRM); packet_put_string(pctx->h_k_cid_sessid, pctx->h_k_cid_sessid_len); packet_send(); /* Expect confirmation from peer */ dispatch_set(SSH2_MSG_USERAUTH_JPAKE_SERVER_CONFIRM, input_userauth_jpake_server_confirm); } /* ARGSUSED */ void input_userauth_jpake_server_confirm(int type, u_int32_t seq, void *ctxt) { Authctxt *authctxt = ctxt; struct jpake_ctx *pctx = authctxt->methoddata; /* Disable this message */ dispatch_set(SSH2_MSG_USERAUTH_JPAKE_SERVER_CONFIRM, NULL); pctx->h_k_sid_sessid = packet_get_string(&pctx->h_k_sid_sessid_len); packet_check_eom(); JPAKE_DEBUG_CTX((pctx, "confirm received in %s", __func__)); /* Verify expected confirmation hash */ if (jpake_check_confirm(pctx->k, pctx->server_id, pctx->server_id_len, session_id2, session_id2_len, pctx->h_k_sid_sessid, pctx->h_k_sid_sessid_len) == 1) debug("%s: %s success", __func__, authctxt->method->name); else { debug("%s: confirmation mismatch", __func__); /* XXX stash this so if auth succeeds then we can warn/kill */ } userauth_jpake_cleanup(authctxt); } #endif /* JPAKE */ static int identity_sign(Identity *id, u_char **sigp, u_int *lenp, u_char *data, u_int datalen) { Key *prv; int ret; /* the agent supports this key */ if (id->ac) return (ssh_agent_sign(id->ac, id->key, sigp, lenp, data, datalen)); /* * we have already loaded the private key or * the private key is stored in external hardware */ if (id->isprivate || (id->key->flags & KEY_FLAG_EXT)) return (key_sign(id->key, sigp, lenp, data, datalen)); /* load the private key from the file */ if ((prv = load_identity_file(id->filename)) == NULL) return (-1); ret = key_sign(prv, sigp, lenp, data, datalen); key_free(prv); return (ret); } static int sign_and_send_pubkey(Authctxt *authctxt, Identity *id) { Buffer b; u_char *blob, *signature; u_int bloblen, slen; u_int skip = 0; int ret = -1; int have_sig = 1; char *fp; fp = key_fingerprint(id->key, SSH_FP_MD5, SSH_FP_HEX); debug3("sign_and_send_pubkey: %s %s", key_type(id->key), fp); xfree(fp); if (key_to_blob(id->key, &blob, &bloblen) == 0) { /* we cannot handle this key */ debug3("sign_and_send_pubkey: cannot handle key"); return 0; } /* data to be signed */ buffer_init(&b); if (datafellows & SSH_OLD_SESSIONID) { buffer_append(&b, session_id2, session_id2_len); skip = session_id2_len; } else { buffer_put_string(&b, session_id2, session_id2_len); skip = buffer_len(&b); } buffer_put_char(&b, SSH2_MSG_USERAUTH_REQUEST); buffer_put_cstring(&b, authctxt->server_user); buffer_put_cstring(&b, datafellows & SSH_BUG_PKSERVICE ? "ssh-userauth" : authctxt->service); if (datafellows & SSH_BUG_PKAUTH) { buffer_put_char(&b, have_sig); } else { buffer_put_cstring(&b, authctxt->method->name); buffer_put_char(&b, have_sig); buffer_put_cstring(&b, key_ssh_name(id->key)); } buffer_put_string(&b, blob, bloblen); /* generate signature */ ret = identity_sign(id, &signature, &slen, buffer_ptr(&b), buffer_len(&b)); if (ret == -1) { xfree(blob); buffer_free(&b); return 0; } #ifdef DEBUG_PK buffer_dump(&b); #endif if (datafellows & SSH_BUG_PKSERVICE) { buffer_clear(&b); buffer_append(&b, session_id2, session_id2_len); skip = session_id2_len; buffer_put_char(&b, SSH2_MSG_USERAUTH_REQUEST); buffer_put_cstring(&b, authctxt->server_user); buffer_put_cstring(&b, authctxt->service); buffer_put_cstring(&b, authctxt->method->name); buffer_put_char(&b, have_sig); if (!(datafellows & SSH_BUG_PKAUTH)) buffer_put_cstring(&b, key_ssh_name(id->key)); buffer_put_string(&b, blob, bloblen); } xfree(blob); /* append signature */ buffer_put_string(&b, signature, slen); xfree(signature); /* skip session id and packet type */ if (buffer_len(&b) < skip + 1) fatal("userauth_pubkey: internal error"); buffer_consume(&b, skip + 1); /* put remaining data from buffer into packet */ packet_start(SSH2_MSG_USERAUTH_REQUEST); packet_put_raw(buffer_ptr(&b), buffer_len(&b)); buffer_free(&b); packet_send(); return 1; } static int send_pubkey_test(Authctxt *authctxt, Identity *id) { u_char *blob; u_int bloblen, have_sig = 0; debug3("send_pubkey_test"); if (key_to_blob(id->key, &blob, &bloblen) == 0) { /* we cannot handle this key */ debug3("send_pubkey_test: cannot handle key"); return 0; } /* register callback for USERAUTH_PK_OK message */ dispatch_set(SSH2_MSG_USERAUTH_PK_OK, &input_userauth_pk_ok); packet_start(SSH2_MSG_USERAUTH_REQUEST); packet_put_cstring(authctxt->server_user); packet_put_cstring(authctxt->service); packet_put_cstring(authctxt->method->name); packet_put_char(have_sig); if (!(datafellows & SSH_BUG_PKAUTH)) packet_put_cstring(key_ssh_name(id->key)); packet_put_string(blob, bloblen); xfree(blob); packet_send(); return 1; } static Key * load_identity_file(char *filename) { Key *private; char prompt[300], *passphrase; int perm_ok = 0, quit, i; struct stat st; if (stat(filename, &st) < 0) { debug3("no such identity: %s", filename); return NULL; } private = key_load_private_type(KEY_UNSPEC, filename, "", NULL, &perm_ok); if (!perm_ok) return NULL; if (private == NULL) { if (options.batch_mode) return NULL; snprintf(prompt, sizeof prompt, "Enter passphrase for key '%.100s': ", filename); for (i = 0; i < options.number_of_password_prompts; i++) { passphrase = read_passphrase(prompt, 0); if (strcmp(passphrase, "") != 0) { private = key_load_private_type(KEY_UNSPEC, filename, passphrase, NULL, NULL); quit = 0; } else { debug2("no passphrase given, try next key"); quit = 1; } memset(passphrase, 0, strlen(passphrase)); xfree(passphrase); if (private != NULL || quit) break; debug2("bad passphrase given, try again..."); } } return private; } /* * try keys in the following order: * 1. agent keys that are found in the config file * 2. other agent keys * 3. keys that are only listed in the config file */ static void pubkey_prepare(Authctxt *authctxt) { Identity *id; Idlist agent, files, *preferred; Key *key; AuthenticationConnection *ac; char *comment; int i, found; TAILQ_INIT(&agent); /* keys from the agent */ TAILQ_INIT(&files); /* keys from the config file */ preferred = &authctxt->keys; TAILQ_INIT(preferred); /* preferred order of keys */ /* list of keys stored in the filesystem */ for (i = 0; i < options.num_identity_files; i++) { key = options.identity_keys[i]; if (key && key->type == KEY_RSA1) continue; if (key && key->cert && key->cert->type != SSH2_CERT_TYPE_USER) continue; options.identity_keys[i] = NULL; id = xcalloc(1, sizeof(*id)); id->key = key; id->filename = xstrdup(options.identity_files[i]); TAILQ_INSERT_TAIL(&files, id, next); } /* list of keys supported by the agent */ if ((ac = ssh_get_authentication_connection())) { for (key = ssh_get_first_identity(ac, &comment, 2); key != NULL; key = ssh_get_next_identity(ac, &comment, 2)) { found = 0; TAILQ_FOREACH(id, &files, next) { /* agent keys from the config file are preferred */ if (key_equal(key, id->key)) { key_free(key); xfree(comment); TAILQ_REMOVE(&files, id, next); TAILQ_INSERT_TAIL(preferred, id, next); id->ac = ac; found = 1; break; } } if (!found && !options.identities_only) { id = xcalloc(1, sizeof(*id)); id->key = key; id->filename = comment; id->ac = ac; TAILQ_INSERT_TAIL(&agent, id, next); } } /* append remaining agent keys */ for (id = TAILQ_FIRST(&agent); id; id = TAILQ_FIRST(&agent)) { TAILQ_REMOVE(&agent, id, next); TAILQ_INSERT_TAIL(preferred, id, next); } authctxt->agent = ac; } /* append remaining keys from the config file */ for (id = TAILQ_FIRST(&files); id; id = TAILQ_FIRST(&files)) { TAILQ_REMOVE(&files, id, next); TAILQ_INSERT_TAIL(preferred, id, next); } TAILQ_FOREACH(id, preferred, next) { debug2("key: %s (%p)", id->filename, id->key); } } static void pubkey_cleanup(Authctxt *authctxt) { Identity *id; if (authctxt->agent != NULL) ssh_close_authentication_connection(authctxt->agent); for (id = TAILQ_FIRST(&authctxt->keys); id; id = TAILQ_FIRST(&authctxt->keys)) { TAILQ_REMOVE(&authctxt->keys, id, next); if (id->key) key_free(id->key); if (id->filename) xfree(id->filename); xfree(id); } } int userauth_pubkey(Authctxt *authctxt) { Identity *id; int sent = 0; while ((id = TAILQ_FIRST(&authctxt->keys))) { if (id->tried++) return (0); /* move key to the end of the queue */ TAILQ_REMOVE(&authctxt->keys, id, next); TAILQ_INSERT_TAIL(&authctxt->keys, id, next); /* * send a test message if we have the public key. for * encrypted keys we cannot do this and have to load the * private key instead */ if (id->key && id->key->type != KEY_RSA1) { debug("Offering %s public key: %s", key_type(id->key), id->filename); sent = send_pubkey_test(authctxt, id); } else if (id->key == NULL) { debug("Trying private key: %s", id->filename); id->key = load_identity_file(id->filename); if (id->key != NULL) { id->isprivate = 1; sent = sign_and_send_pubkey(authctxt, id); key_free(id->key); id->key = NULL; } } if (sent) return (sent); } return (0); } /* * Send userauth request message specifying keyboard-interactive method. */ int userauth_kbdint(Authctxt *authctxt) { static int attempt = 0; if (attempt++ >= options.number_of_password_prompts) return 0; /* disable if no SSH2_MSG_USERAUTH_INFO_REQUEST has been seen */ if (attempt > 1 && !authctxt->info_req_seen) { debug3("userauth_kbdint: disable: no info_req_seen"); dispatch_set(SSH2_MSG_USERAUTH_INFO_REQUEST, NULL); return 0; } debug2("userauth_kbdint"); packet_start(SSH2_MSG_USERAUTH_REQUEST); packet_put_cstring(authctxt->server_user); packet_put_cstring(authctxt->service); packet_put_cstring(authctxt->method->name); packet_put_cstring(""); /* lang */ packet_put_cstring(options.kbd_interactive_devices ? options.kbd_interactive_devices : ""); packet_send(); dispatch_set(SSH2_MSG_USERAUTH_INFO_REQUEST, &input_userauth_info_req); return 1; } /* * parse INFO_REQUEST, prompt user and send INFO_RESPONSE */ void input_userauth_info_req(int type, u_int32_t seq, void *ctxt) { Authctxt *authctxt = ctxt; char *name, *inst, *lang, *prompt, *response; u_int num_prompts, i; int echo = 0; debug2("input_userauth_info_req"); if (authctxt == NULL) fatal("input_userauth_info_req: no authentication context"); authctxt->info_req_seen = 1; name = packet_get_string(NULL); inst = packet_get_string(NULL); lang = packet_get_string(NULL); if (strlen(name) > 0) logit("%s", name); if (strlen(inst) > 0) logit("%s", inst); xfree(name); xfree(inst); xfree(lang); num_prompts = packet_get_int(); /* * Begin to build info response packet based on prompts requested. * We commit to providing the correct number of responses, so if * further on we run into a problem that prevents this, we have to * be sure and clean this up and send a correct error response. */ packet_start(SSH2_MSG_USERAUTH_INFO_RESPONSE); packet_put_int(num_prompts); debug2("input_userauth_info_req: num_prompts %d", num_prompts); for (i = 0; i < num_prompts; i++) { prompt = packet_get_string(NULL); echo = packet_get_char(); response = read_passphrase(prompt, echo ? RP_ECHO : 0); packet_put_cstring(response); memset(response, 0, strlen(response)); xfree(response); xfree(prompt); } packet_check_eom(); /* done with parsing incoming message. */ packet_add_padding(64); packet_send(); } static int ssh_keysign(Key *key, u_char **sigp, u_int *lenp, u_char *data, u_int datalen) { Buffer b; struct stat st; pid_t pid; int to[2], from[2], status, version = 2; debug2("ssh_keysign called"); if (stat(_PATH_SSH_KEY_SIGN, &st) < 0) { error("ssh_keysign: not installed: %s", strerror(errno)); return -1; } if (fflush(stdout) != 0) error("ssh_keysign: fflush: %s", strerror(errno)); if (pipe(to) < 0) { error("ssh_keysign: pipe: %s", strerror(errno)); return -1; } if (pipe(from) < 0) { error("ssh_keysign: pipe: %s", strerror(errno)); return -1; } if ((pid = fork()) < 0) { error("ssh_keysign: fork: %s", strerror(errno)); return -1; } if (pid == 0) { /* keep the socket on exec */ fcntl(packet_get_connection_in(), F_SETFD, 0); permanently_drop_suid(getuid()); close(from[0]); if (dup2(from[1], STDOUT_FILENO) < 0) fatal("ssh_keysign: dup2: %s", strerror(errno)); close(to[1]); if (dup2(to[0], STDIN_FILENO) < 0) fatal("ssh_keysign: dup2: %s", strerror(errno)); close(from[1]); close(to[0]); execl(_PATH_SSH_KEY_SIGN, _PATH_SSH_KEY_SIGN, (char *) 0); fatal("ssh_keysign: exec(%s): %s", _PATH_SSH_KEY_SIGN, strerror(errno)); } close(from[1]); close(to[0]); buffer_init(&b); buffer_put_int(&b, packet_get_connection_in()); /* send # of socket */ buffer_put_string(&b, data, datalen); if (ssh_msg_send(to[1], version, &b) == -1) fatal("ssh_keysign: couldn't send request"); if (ssh_msg_recv(from[0], &b) < 0) { error("ssh_keysign: no reply"); buffer_free(&b); return -1; } close(from[0]); close(to[1]); while (waitpid(pid, &status, 0) < 0) if (errno != EINTR) break; if (buffer_get_char(&b) != version) { error("ssh_keysign: bad version"); buffer_free(&b); return -1; } *sigp = buffer_get_string(&b, lenp); buffer_free(&b); return 0; } int userauth_hostbased(Authctxt *authctxt) { Key *private = NULL; Sensitive *sensitive = authctxt->sensitive; Buffer b; u_char *signature, *blob; char *chost, *pkalg, *p; const char *service; u_int blen, slen; int ok, i, found = 0; /* check for a useful key */ for (i = 0; i < sensitive->nkeys; i++) { private = sensitive->keys[i]; if (private && private->type != KEY_RSA1) { found = 1; /* we take and free the key */ sensitive->keys[i] = NULL; break; } } if (!found) { debug("No more client hostkeys for hostbased authentication."); return 0; } if (key_to_blob(private, &blob, &blen) == 0) { key_free(private); return 0; } /* figure out a name for the client host */ p = get_local_name(packet_get_connection_in()); if (p == NULL) { error("userauth_hostbased: cannot get local ipaddr/name"); key_free(private); xfree(blob); return 0; } xasprintf(&chost, "%s.", p); debug2("userauth_hostbased: chost %s", chost); xfree(p); service = datafellows & SSH_BUG_HBSERVICE ? "ssh-userauth" : authctxt->service; pkalg = xstrdup(key_ssh_name(private)); buffer_init(&b); /* construct data */ buffer_put_string(&b, session_id2, session_id2_len); buffer_put_char(&b, SSH2_MSG_USERAUTH_REQUEST); buffer_put_cstring(&b, authctxt->server_user); buffer_put_cstring(&b, service); buffer_put_cstring(&b, authctxt->method->name); buffer_put_cstring(&b, pkalg); buffer_put_string(&b, blob, blen); buffer_put_cstring(&b, chost); buffer_put_cstring(&b, authctxt->local_user); #ifdef DEBUG_PK buffer_dump(&b); #endif if (sensitive->external_keysign) ok = ssh_keysign(private, &signature, &slen, buffer_ptr(&b), buffer_len(&b)); else ok = key_sign(private, &signature, &slen, buffer_ptr(&b), buffer_len(&b)); key_free(private); buffer_free(&b); if (ok != 0) { error("key_sign failed"); xfree(chost); xfree(pkalg); xfree(blob); return 0; } packet_start(SSH2_MSG_USERAUTH_REQUEST); packet_put_cstring(authctxt->server_user); packet_put_cstring(authctxt->service); packet_put_cstring(authctxt->method->name); packet_put_cstring(pkalg); packet_put_string(blob, blen); packet_put_cstring(chost); packet_put_cstring(authctxt->local_user); packet_put_string(signature, slen); memset(signature, 's', slen); xfree(signature); xfree(chost); xfree(pkalg); xfree(blob); packet_send(); return 1; } #ifdef JPAKE int userauth_jpake(Authctxt *authctxt) { struct jpake_ctx *pctx; u_char *x1_proof, *x2_proof; u_int x1_proof_len, x2_proof_len; static int attempt = 0; /* XXX share with userauth_password's? */ if (attempt++ >= options.number_of_password_prompts) return 0; if (attempt != 1) error("Permission denied, please try again."); if (authctxt->methoddata != NULL) fatal("%s: authctxt->methoddata already set (%p)", __func__, authctxt->methoddata); authctxt->methoddata = pctx = jpake_new(); /* * Send request immediately, to get the protocol going while * we do the initial computations. */ packet_start(SSH2_MSG_USERAUTH_REQUEST); packet_put_cstring(authctxt->server_user); packet_put_cstring(authctxt->service); packet_put_cstring(authctxt->method->name); packet_send(); packet_write_wait(); jpake_step1(pctx->grp, &pctx->client_id, &pctx->client_id_len, &pctx->x1, &pctx->x2, &pctx->g_x1, &pctx->g_x2, &x1_proof, &x1_proof_len, &x2_proof, &x2_proof_len); JPAKE_DEBUG_CTX((pctx, "step 1 sending in %s", __func__)); packet_start(SSH2_MSG_USERAUTH_JPAKE_CLIENT_STEP1); packet_put_string(pctx->client_id, pctx->client_id_len); packet_put_bignum2(pctx->g_x1); packet_put_bignum2(pctx->g_x2); packet_put_string(x1_proof, x1_proof_len); packet_put_string(x2_proof, x2_proof_len); packet_send(); bzero(x1_proof, x1_proof_len); bzero(x2_proof, x2_proof_len); xfree(x1_proof); xfree(x2_proof); /* Expect step 1 packet from peer */ dispatch_set(SSH2_MSG_USERAUTH_JPAKE_SERVER_STEP1, input_userauth_jpake_server_step1); dispatch_set(SSH2_MSG_USERAUTH_SUCCESS, &input_userauth_success_unexpected); return 1; } void userauth_jpake_cleanup(Authctxt *authctxt) { debug3("%s: clean up", __func__); if (authctxt->methoddata != NULL) { jpake_free(authctxt->methoddata); authctxt->methoddata = NULL; } dispatch_set(SSH2_MSG_USERAUTH_SUCCESS, &input_userauth_success); } #endif /* JPAKE */ /* find auth method */ /* * given auth method name, if configurable options permit this method fill * in auth_ident field and return true, otherwise return false. */ static int authmethod_is_enabled(Authmethod *method) { if (method == NULL) return 0; /* return false if options indicate this method is disabled */ if (method->enabled == NULL || *method->enabled == 0) return 0; /* return false if batch mode is enabled but method needs interactive mode */ if (method->batch_flag != NULL && *method->batch_flag != 0) return 0; return 1; } static Authmethod * authmethod_lookup(const char *name) { Authmethod *method = NULL; if (name != NULL) for (method = authmethods; method->name != NULL; method++) if (strcmp(name, method->name) == 0) return method; debug2("Unrecognized authentication method name: %s", name ? name : "NULL"); return NULL; } /* XXX internal state */ static Authmethod *current = NULL; static char *supported = NULL; static char *preferred = NULL; /* * Given the authentication method list sent by the server, return the * next method we should try. If the server initially sends a nil list, * use a built-in default list. */ static Authmethod * authmethod_get(char *authlist) { char *name = NULL; u_int next; /* Use a suitable default if we're passed a nil list. */ if (authlist == NULL || strlen(authlist) == 0) authlist = options.preferred_authentications; if (supported == NULL || strcmp(authlist, supported) != 0) { debug3("start over, passed a different list %s", authlist); if (supported != NULL) xfree(supported); supported = xstrdup(authlist); preferred = options.preferred_authentications; debug3("preferred %s", preferred); current = NULL; } else if (current != NULL && authmethod_is_enabled(current)) return current; for (;;) { if ((name = match_list(preferred, supported, &next)) == NULL) { debug("No more authentication methods to try."); current = NULL; return NULL; } preferred += next; debug3("authmethod_lookup %s", name); debug3("remaining preferred: %s", preferred); if ((current = authmethod_lookup(name)) != NULL && authmethod_is_enabled(current)) { debug3("authmethod_is_enabled %s", name); debug("Next authentication method: %s", name); return current; } } } static char * authmethods_get(void) { Authmethod *method = NULL; Buffer b; char *list; buffer_init(&b); for (method = authmethods; method->name != NULL; method++) { if (authmethod_is_enabled(method)) { if (buffer_len(&b) > 0) buffer_append(&b, ",", 1); buffer_append(&b, method->name, strlen(method->name)); } } buffer_append(&b, "\0", 1); list = xstrdup(buffer_ptr(&b)); buffer_free(&b); return list; } diff --git a/crypto/openssh/sshd.c b/crypto/openssh/sshd.c index 18d2d8e88be1..a6c9943e4fa4 100644 --- a/crypto/openssh/sshd.c +++ b/crypto/openssh/sshd.c @@ -1,2387 +1,2404 @@ /* $OpenBSD: sshd.c,v 1.381 2011/01/11 06:13:10 djm Exp $ */ +/* $FreeBSD$ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved * This program is the ssh daemon. It listens for connections from clients, * and performs authentication, executes use commands or shell, and forwards * information to/from the application to the user client over an encrypted * connection. This can also handle forwarding of X11, TCP/IP, and * authentication agent connections. * * As far as I am concerned, the code I have written for this software * can be used freely for any purpose. Any derived versions of this * software must be clearly marked as such, and if the derived work is * incompatible with the protocol description in the RFC file, it must be * called by a name other than "ssh" or "Secure Shell". * * SSH2 implementation: * Privilege Separation: * * Copyright (c) 2000, 2001, 2002 Markus Friedl. All rights reserved. * Copyright (c) 2002 Niels Provos. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "includes.h" __RCSID("$FreeBSD$"); #include #include #include #include #ifdef HAVE_SYS_STAT_H # include #endif #ifdef HAVE_SYS_TIME_H # include #endif #include "openbsd-compat/sys-tree.h" #include "openbsd-compat/sys-queue.h" #include #include #include #include #ifdef HAVE_PATHS_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include "openbsd-compat/openssl-compat.h" #ifdef HAVE_SECUREWARE #include #include #endif #ifdef __FreeBSD__ #include #if defined(GSSAPI) && defined(HAVE_GSSAPI_H) #include #elif defined(GSSAPI) && defined(HAVE_GSSAPI_GSSAPI_H) #include #endif #endif #include "xmalloc.h" #include "ssh.h" #include "ssh1.h" #include "ssh2.h" #include "rsa.h" #include "sshpty.h" #include "packet.h" #include "log.h" #include "buffer.h" #include "servconf.h" #include "uidswap.h" #include "compat.h" #include "cipher.h" #include "key.h" #include "kex.h" #include "dh.h" #include "myproposal.h" #include "authfile.h" #include "pathnames.h" #include "atomicio.h" #include "canohost.h" #include "hostfile.h" #include "auth.h" #include "misc.h" #include "msg.h" #include "dispatch.h" #include "channels.h" #include "session.h" #include "monitor_mm.h" #include "monitor.h" #ifdef GSSAPI #include "ssh-gss.h" #endif #include "monitor_wrap.h" #include "roaming.h" #include "version.h" #ifdef LIBWRAP #include #include int allow_severity; int deny_severity; #endif /* LIBWRAP */ #ifndef O_NOCTTY #define O_NOCTTY 0 #endif /* Re-exec fds */ #define REEXEC_DEVCRYPTO_RESERVED_FD (STDERR_FILENO + 1) #define REEXEC_STARTUP_PIPE_FD (STDERR_FILENO + 2) #define REEXEC_CONFIG_PASS_FD (STDERR_FILENO + 3) #define REEXEC_MIN_FREE_FD (STDERR_FILENO + 4) extern char *__progname; /* Server configuration options. */ ServerOptions options; /* Name of the server configuration file. */ char *config_file_name = _PATH_SERVER_CONFIG_FILE; /* * Debug mode flag. This can be set on the command line. If debug * mode is enabled, extra debugging output will be sent to the system * log, the daemon will not go to background, and will exit after processing * the first connection. */ int debug_flag = 0; /* Flag indicating that the daemon should only test the configuration and keys. */ int test_flag = 0; /* Flag indicating that the daemon is being started from inetd. */ int inetd_flag = 0; /* Flag indicating that sshd should not detach and become a daemon. */ int no_daemon_flag = 0; /* debug goes to stderr unless inetd_flag is set */ int log_stderr = 0; /* Saved arguments to main(). */ char **saved_argv; int saved_argc; /* re-exec */ int rexeced_flag = 0; int rexec_flag = 1; int rexec_argc = 0; char **rexec_argv; /* * The sockets that the server is listening; this is used in the SIGHUP * signal handler. */ #define MAX_LISTEN_SOCKS 16 int listen_socks[MAX_LISTEN_SOCKS]; int num_listen_socks = 0; /* * the client's version string, passed by sshd2 in compat mode. if != NULL, * sshd will skip the version-number exchange */ char *client_version_string = NULL; char *server_version_string = NULL; /* for rekeying XXX fixme */ Kex *xxx_kex; /* * Any really sensitive data in the application is contained in this * structure. The idea is that this structure could be locked into memory so * that the pages do not get written into swap. However, there are some * problems. The private key contains BIGNUMs, and we do not (in principle) * have access to the internals of them, and locking just the structure is * not very useful. Currently, memory locking is not implemented. */ struct { Key *server_key; /* ephemeral server key */ Key *ssh1_host_key; /* ssh1 host key */ Key **host_keys; /* all private host keys */ Key **host_certificates; /* all public host certificates */ int have_ssh1_key; int have_ssh2_key; u_char ssh1_cookie[SSH_SESSION_KEY_LENGTH]; } sensitive_data; /* * Flag indicating whether the RSA server key needs to be regenerated. * Is set in the SIGALRM handler and cleared when the key is regenerated. */ static volatile sig_atomic_t key_do_regen = 0; /* This is set to true when a signal is received. */ static volatile sig_atomic_t received_sighup = 0; static volatile sig_atomic_t received_sigterm = 0; /* session identifier, used by RSA-auth */ u_char session_id[16]; /* same for ssh2 */ u_char *session_id2 = NULL; u_int session_id2_len = 0; /* record remote hostname or ip */ u_int utmp_len = MAXHOSTNAMELEN; /* options.max_startup sized array of fd ints */ int *startup_pipes = NULL; int startup_pipe; /* in child */ /* variables used for privilege separation */ int use_privsep = -1; struct monitor *pmonitor = NULL; /* global authentication context */ Authctxt *the_authctxt = NULL; /* sshd_config buffer */ Buffer cfg; /* message to be displayed after login */ Buffer loginmsg; /* Unprivileged user */ struct passwd *privsep_pw = NULL; /* Prototypes for various functions defined later in this file. */ void destroy_sensitive_data(void); void demote_sensitive_data(void); static void do_ssh1_kex(void); static void do_ssh2_kex(void); /* * Close all listening sockets */ static void close_listen_socks(void) { int i; for (i = 0; i < num_listen_socks; i++) close(listen_socks[i]); num_listen_socks = -1; } static void close_startup_pipes(void) { int i; if (startup_pipes) for (i = 0; i < options.max_startups; i++) if (startup_pipes[i] != -1) close(startup_pipes[i]); } /* * Signal handler for SIGHUP. Sshd execs itself when it receives SIGHUP; * the effect is to reread the configuration file (and to regenerate * the server key). */ /*ARGSUSED*/ static void sighup_handler(int sig) { int save_errno = errno; received_sighup = 1; signal(SIGHUP, sighup_handler); errno = save_errno; } /* * Called from the main program after receiving SIGHUP. * Restarts the server. */ static void sighup_restart(void) { logit("Received SIGHUP; restarting."); close_listen_socks(); close_startup_pipes(); alarm(0); /* alarm timer persists across exec */ signal(SIGHUP, SIG_IGN); /* will be restored after exec */ execv(saved_argv[0], saved_argv); logit("RESTART FAILED: av[0]='%.100s', error: %.100s.", saved_argv[0], strerror(errno)); exit(1); } /* * Generic signal handler for terminating signals in the master daemon. */ /*ARGSUSED*/ static void sigterm_handler(int sig) { received_sigterm = sig; } /* * SIGCHLD handler. This is called whenever a child dies. This will then * reap any zombies left by exited children. */ /*ARGSUSED*/ static void main_sigchld_handler(int sig) { int save_errno = errno; pid_t pid; int status; while ((pid = waitpid(-1, &status, WNOHANG)) > 0 || (pid < 0 && errno == EINTR)) ; signal(SIGCHLD, main_sigchld_handler); errno = save_errno; } /* * Signal handler for the alarm after the login grace period has expired. */ /*ARGSUSED*/ static void grace_alarm_handler(int sig) { if (use_privsep && pmonitor != NULL && pmonitor->m_pid > 0) kill(pmonitor->m_pid, SIGALRM); /* Log error and exit. */ sigdie("Timeout before authentication for %s", get_remote_ipaddr()); } /* * Signal handler for the key regeneration alarm. Note that this * alarm only occurs in the daemon waiting for connections, and it does not * do anything with the private key or random state before forking. * Thus there should be no concurrency control/asynchronous execution * problems. */ static void generate_ephemeral_server_key(void) { verbose("Generating %s%d bit RSA key.", sensitive_data.server_key ? "new " : "", options.server_key_bits); if (sensitive_data.server_key != NULL) key_free(sensitive_data.server_key); sensitive_data.server_key = key_generate(KEY_RSA1, options.server_key_bits); verbose("RSA key generation complete."); arc4random_buf(sensitive_data.ssh1_cookie, SSH_SESSION_KEY_LENGTH); arc4random_stir(); } /*ARGSUSED*/ static void key_regeneration_alarm(int sig) { int save_errno = errno; signal(SIGALRM, SIG_DFL); errno = save_errno; key_do_regen = 1; } static void sshd_exchange_identification(int sock_in, int sock_out) { u_int i; int mismatch; int remote_major, remote_minor; int major, minor; char *s, *newline = "\n"; char buf[256]; /* Must not be larger than remote_version. */ char remote_version[256]; /* Must be at least as big as buf. */ if ((options.protocol & SSH_PROTO_1) && (options.protocol & SSH_PROTO_2)) { major = PROTOCOL_MAJOR_1; minor = 99; } else if (options.protocol & SSH_PROTO_2) { major = PROTOCOL_MAJOR_2; minor = PROTOCOL_MINOR_2; newline = "\r\n"; } else { major = PROTOCOL_MAJOR_1; minor = PROTOCOL_MINOR_1; } snprintf(buf, sizeof buf, "SSH-%d.%d-%.100s%s", major, minor, - SSH_VERSION, newline); + SSH_RELEASE, newline); server_version_string = xstrdup(buf); /* Send our protocol version identification. */ if (roaming_atomicio(vwrite, sock_out, server_version_string, strlen(server_version_string)) != strlen(server_version_string)) { logit("Could not write ident string to %s", get_remote_ipaddr()); cleanup_exit(255); } /* Read other sides version identification. */ memset(buf, 0, sizeof(buf)); for (i = 0; i < sizeof(buf) - 1; i++) { if (roaming_atomicio(read, sock_in, &buf[i], 1) != 1) { logit("Did not receive identification string from %s", get_remote_ipaddr()); cleanup_exit(255); } if (buf[i] == '\r') { buf[i] = 0; /* Kludge for F-Secure Macintosh < 1.0.2 */ if (i == 12 && strncmp(buf, "SSH-1.5-W1.0", 12) == 0) break; continue; } if (buf[i] == '\n') { buf[i] = 0; break; } } buf[sizeof(buf) - 1] = 0; client_version_string = xstrdup(buf); /* * Check that the versions match. In future this might accept * several versions and set appropriate flags to handle them. */ if (sscanf(client_version_string, "SSH-%d.%d-%[^\n]\n", &remote_major, &remote_minor, remote_version) != 3) { s = "Protocol mismatch.\n"; (void) atomicio(vwrite, sock_out, s, strlen(s)); close(sock_in); close(sock_out); logit("Bad protocol version identification '%.100s' from %s", client_version_string, get_remote_ipaddr()); cleanup_exit(255); } debug("Client protocol version %d.%d; client software version %.100s", remote_major, remote_minor, remote_version); compat_datafellows(remote_version); if (datafellows & SSH_BUG_PROBE) { logit("probed from %s with %s. Don't panic.", get_remote_ipaddr(), client_version_string); cleanup_exit(255); } if (datafellows & SSH_BUG_SCANNER) { logit("scanned from %s with %s. Don't panic.", get_remote_ipaddr(), client_version_string); cleanup_exit(255); } mismatch = 0; switch (remote_major) { case 1: if (remote_minor == 99) { if (options.protocol & SSH_PROTO_2) enable_compat20(); else mismatch = 1; break; } if (!(options.protocol & SSH_PROTO_1)) { mismatch = 1; break; } if (remote_minor < 3) { packet_disconnect("Your ssh version is too old and " "is no longer supported. Please install a newer version."); } else if (remote_minor == 3) { /* note that this disables agent-forwarding */ enable_compat13(); } break; case 2: if (options.protocol & SSH_PROTO_2) { enable_compat20(); break; } /* FALLTHROUGH */ default: mismatch = 1; break; } chop(server_version_string); debug("Local version string %.200s", server_version_string); if (mismatch) { s = "Protocol major versions differ.\n"; (void) atomicio(vwrite, sock_out, s, strlen(s)); close(sock_in); close(sock_out); logit("Protocol major versions differ for %s: %.200s vs. %.200s", get_remote_ipaddr(), server_version_string, client_version_string); cleanup_exit(255); } } /* Destroy the host and server keys. They will no longer be needed. */ void destroy_sensitive_data(void) { int i; if (sensitive_data.server_key) { key_free(sensitive_data.server_key); sensitive_data.server_key = NULL; } for (i = 0; i < options.num_host_key_files; i++) { if (sensitive_data.host_keys[i]) { key_free(sensitive_data.host_keys[i]); sensitive_data.host_keys[i] = NULL; } if (sensitive_data.host_certificates[i]) { key_free(sensitive_data.host_certificates[i]); sensitive_data.host_certificates[i] = NULL; } } sensitive_data.ssh1_host_key = NULL; memset(sensitive_data.ssh1_cookie, 0, SSH_SESSION_KEY_LENGTH); } /* Demote private to public keys for network child */ void demote_sensitive_data(void) { Key *tmp; int i; if (sensitive_data.server_key) { tmp = key_demote(sensitive_data.server_key); key_free(sensitive_data.server_key); sensitive_data.server_key = tmp; } for (i = 0; i < options.num_host_key_files; i++) { if (sensitive_data.host_keys[i]) { tmp = key_demote(sensitive_data.host_keys[i]); key_free(sensitive_data.host_keys[i]); sensitive_data.host_keys[i] = tmp; if (tmp->type == KEY_RSA1) sensitive_data.ssh1_host_key = tmp; } /* Certs do not need demotion */ } /* We do not clear ssh1_host key and cookie. XXX - Okay Niels? */ } static void privsep_preauth_child(void) { u_int32_t rnd[256]; gid_t gidset[1]; /* Enable challenge-response authentication for privilege separation */ privsep_challenge_enable(); arc4random_stir(); arc4random_buf(rnd, sizeof(rnd)); RAND_seed(rnd, sizeof(rnd)); /* Demote the private keys to public keys. */ demote_sensitive_data(); /* Change our root directory */ if (chroot(_PATH_PRIVSEP_CHROOT_DIR) == -1) fatal("chroot(\"%s\"): %s", _PATH_PRIVSEP_CHROOT_DIR, strerror(errno)); if (chdir("/") == -1) fatal("chdir(\"/\"): %s", strerror(errno)); /* Drop our privileges */ debug3("privsep user:group %u:%u", (u_int)privsep_pw->pw_uid, (u_int)privsep_pw->pw_gid); #if 0 /* XXX not ready, too heavy after chroot */ do_setusercontext(privsep_pw); #else gidset[0] = privsep_pw->pw_gid; if (setgroups(1, gidset) < 0) fatal("setgroups: %.100s", strerror(errno)); permanently_set_uid(privsep_pw); #endif } static int privsep_preauth(Authctxt *authctxt) { int status; pid_t pid; /* Set up unprivileged child process to deal with network data */ pmonitor = monitor_init(); /* Store a pointer to the kex for later rekeying */ pmonitor->m_pkex = &xxx_kex; pid = fork(); if (pid == -1) { fatal("fork of unprivileged child failed"); } else if (pid != 0) { debug2("Network child is on pid %ld", (long)pid); close(pmonitor->m_recvfd); pmonitor->m_pid = pid; monitor_child_preauth(authctxt, pmonitor); close(pmonitor->m_sendfd); /* Sync memory */ monitor_sync(pmonitor); /* Wait for the child's exit status */ while (waitpid(pid, &status, 0) < 0) if (errno != EINTR) break; return (1); } else { /* child */ close(pmonitor->m_sendfd); /* Demote the child */ if (getuid() == 0 || geteuid() == 0) privsep_preauth_child(); setproctitle("%s", "[net]"); } return (0); } static void privsep_postauth(Authctxt *authctxt) { u_int32_t rnd[256]; #ifdef DISABLE_FD_PASSING if (1) { #else if (authctxt->pw->pw_uid == 0 || options.use_login) { #endif /* File descriptor passing is broken or root login */ use_privsep = 0; goto skip; } /* New socket pair */ monitor_reinit(pmonitor); pmonitor->m_pid = fork(); if (pmonitor->m_pid == -1) fatal("fork of unprivileged child failed"); else if (pmonitor->m_pid != 0) { verbose("User child is on pid %ld", (long)pmonitor->m_pid); close(pmonitor->m_recvfd); buffer_clear(&loginmsg); monitor_child_postauth(pmonitor); /* NEVERREACHED */ exit(0); } close(pmonitor->m_sendfd); /* Demote the private keys to public keys. */ demote_sensitive_data(); arc4random_stir(); arc4random_buf(rnd, sizeof(rnd)); RAND_seed(rnd, sizeof(rnd)); /* Drop privileges */ do_setusercontext(authctxt->pw); skip: /* It is safe now to apply the key state */ monitor_apply_keystate(pmonitor); /* * Tell the packet layer that authentication was successful, since * this information is not part of the key state. */ packet_set_authenticated(); } static char * list_hostkey_types(void) { Buffer b; const char *p; char *ret; int i; Key *key; buffer_init(&b); for (i = 0; i < options.num_host_key_files; i++) { key = sensitive_data.host_keys[i]; if (key == NULL) continue; switch (key->type) { case KEY_RSA: case KEY_DSA: case KEY_ECDSA: if (buffer_len(&b) > 0) buffer_append(&b, ",", 1); p = key_ssh_name(key); buffer_append(&b, p, strlen(p)); break; } /* If the private key has a cert peer, then list that too */ key = sensitive_data.host_certificates[i]; if (key == NULL) continue; switch (key->type) { case KEY_RSA_CERT_V00: case KEY_DSA_CERT_V00: case KEY_RSA_CERT: case KEY_DSA_CERT: case KEY_ECDSA_CERT: if (buffer_len(&b) > 0) buffer_append(&b, ",", 1); p = key_ssh_name(key); buffer_append(&b, p, strlen(p)); break; } } buffer_append(&b, "\0", 1); ret = xstrdup(buffer_ptr(&b)); buffer_free(&b); debug("list_hostkey_types: %s", ret); return ret; } static Key * get_hostkey_by_type(int type, int need_private) { int i; Key *key; for (i = 0; i < options.num_host_key_files; i++) { switch (type) { case KEY_RSA_CERT_V00: case KEY_DSA_CERT_V00: case KEY_RSA_CERT: case KEY_DSA_CERT: case KEY_ECDSA_CERT: key = sensitive_data.host_certificates[i]; break; default: key = sensitive_data.host_keys[i]; break; } if (key != NULL && key->type == type) return need_private ? sensitive_data.host_keys[i] : key; } return NULL; } Key * get_hostkey_public_by_type(int type) { return get_hostkey_by_type(type, 0); } Key * get_hostkey_private_by_type(int type) { return get_hostkey_by_type(type, 1); } Key * get_hostkey_by_index(int ind) { if (ind < 0 || ind >= options.num_host_key_files) return (NULL); return (sensitive_data.host_keys[ind]); } int get_hostkey_index(Key *key) { int i; for (i = 0; i < options.num_host_key_files; i++) { if (key_is_cert(key)) { if (key == sensitive_data.host_certificates[i]) return (i); } else { if (key == sensitive_data.host_keys[i]) return (i); } } return (-1); } /* * returns 1 if connection should be dropped, 0 otherwise. * dropping starts at connection #max_startups_begin with a probability * of (max_startups_rate/100). the probability increases linearly until * all connections are dropped for startups > max_startups */ static int drop_connection(int startups) { int p, r; if (startups < options.max_startups_begin) return 0; if (startups >= options.max_startups) return 1; if (options.max_startups_rate == 100) return 1; p = 100 - options.max_startups_rate; p *= startups - options.max_startups_begin; p /= options.max_startups - options.max_startups_begin; p += options.max_startups_rate; r = arc4random_uniform(100); debug("drop_connection: p %d, r %d", p, r); return (r < p) ? 1 : 0; } static void usage(void) { fprintf(stderr, "%s, %s\n", SSH_RELEASE, SSLeay_version(SSLEAY_VERSION)); fprintf(stderr, "usage: sshd [-46DdeiqTt] [-b bits] [-C connection_spec] [-c host_cert_file]\n" " [-f config_file] [-g login_grace_time] [-h host_key_file]\n" " [-k key_gen_time] [-o option] [-p port] [-u len]\n" ); exit(1); } static void send_rexec_state(int fd, Buffer *conf) { Buffer m; debug3("%s: entering fd = %d config len %d", __func__, fd, buffer_len(conf)); /* * Protocol from reexec master to child: * string configuration * u_int ephemeral_key_follows * bignum e (only if ephemeral_key_follows == 1) * bignum n " * bignum d " * bignum iqmp " * bignum p " * bignum q " * string rngseed (only if OpenSSL is not self-seeded) */ buffer_init(&m); buffer_put_cstring(&m, buffer_ptr(conf)); if (sensitive_data.server_key != NULL && sensitive_data.server_key->type == KEY_RSA1) { buffer_put_int(&m, 1); buffer_put_bignum(&m, sensitive_data.server_key->rsa->e); buffer_put_bignum(&m, sensitive_data.server_key->rsa->n); buffer_put_bignum(&m, sensitive_data.server_key->rsa->d); buffer_put_bignum(&m, sensitive_data.server_key->rsa->iqmp); buffer_put_bignum(&m, sensitive_data.server_key->rsa->p); buffer_put_bignum(&m, sensitive_data.server_key->rsa->q); } else buffer_put_int(&m, 0); #ifndef OPENSSL_PRNG_ONLY rexec_send_rng_seed(&m); #endif if (ssh_msg_send(fd, 0, &m) == -1) fatal("%s: ssh_msg_send failed", __func__); buffer_free(&m); debug3("%s: done", __func__); } static void recv_rexec_state(int fd, Buffer *conf) { Buffer m; char *cp; u_int len; debug3("%s: entering fd = %d", __func__, fd); buffer_init(&m); if (ssh_msg_recv(fd, &m) == -1) fatal("%s: ssh_msg_recv failed", __func__); if (buffer_get_char(&m) != 0) fatal("%s: rexec version mismatch", __func__); cp = buffer_get_string(&m, &len); if (conf != NULL) buffer_append(conf, cp, len + 1); xfree(cp); if (buffer_get_int(&m)) { if (sensitive_data.server_key != NULL) key_free(sensitive_data.server_key); sensitive_data.server_key = key_new_private(KEY_RSA1); buffer_get_bignum(&m, sensitive_data.server_key->rsa->e); buffer_get_bignum(&m, sensitive_data.server_key->rsa->n); buffer_get_bignum(&m, sensitive_data.server_key->rsa->d); buffer_get_bignum(&m, sensitive_data.server_key->rsa->iqmp); buffer_get_bignum(&m, sensitive_data.server_key->rsa->p); buffer_get_bignum(&m, sensitive_data.server_key->rsa->q); rsa_generate_additional_parameters( sensitive_data.server_key->rsa); } #ifndef OPENSSL_PRNG_ONLY rexec_recv_rng_seed(&m); #endif buffer_free(&m); debug3("%s: done", __func__); } /* Accept a connection from inetd */ static void server_accept_inetd(int *sock_in, int *sock_out) { int fd; startup_pipe = -1; if (rexeced_flag) { close(REEXEC_CONFIG_PASS_FD); *sock_in = *sock_out = dup(STDIN_FILENO); if (!debug_flag) { startup_pipe = dup(REEXEC_STARTUP_PIPE_FD); close(REEXEC_STARTUP_PIPE_FD); } } else { *sock_in = dup(STDIN_FILENO); *sock_out = dup(STDOUT_FILENO); } /* * We intentionally do not close the descriptors 0, 1, and 2 * as our code for setting the descriptors won't work if * ttyfd happens to be one of those. */ if ((fd = open(_PATH_DEVNULL, O_RDWR, 0)) != -1) { dup2(fd, STDIN_FILENO); dup2(fd, STDOUT_FILENO); if (fd > STDOUT_FILENO) close(fd); } debug("inetd sockets after dupping: %d, %d", *sock_in, *sock_out); } /* * Listen for TCP connections */ static void server_listen(void) { int ret, listen_sock, on = 1; struct addrinfo *ai; char ntop[NI_MAXHOST], strport[NI_MAXSERV]; + int socksize; + socklen_t len; for (ai = options.listen_addrs; ai; ai = ai->ai_next) { if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) continue; if (num_listen_socks >= MAX_LISTEN_SOCKS) fatal("Too many listen sockets. " "Enlarge MAX_LISTEN_SOCKS"); if ((ret = getnameinfo(ai->ai_addr, ai->ai_addrlen, ntop, sizeof(ntop), strport, sizeof(strport), NI_NUMERICHOST|NI_NUMERICSERV)) != 0) { error("getnameinfo failed: %.100s", ssh_gai_strerror(ret)); continue; } /* Create socket for listening. */ listen_sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); if (listen_sock < 0) { /* kernel may not support ipv6 */ verbose("socket: %.100s", strerror(errno)); continue; } if (set_nonblock(listen_sock) == -1) { close(listen_sock); continue; } /* * Set socket options. * Allow local port reuse in TIME_WAIT. */ if (setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) error("setsockopt SO_REUSEADDR: %s", strerror(errno)); /* Only communicate in IPv6 over AF_INET6 sockets. */ if (ai->ai_family == AF_INET6) sock_set_v6only(listen_sock); debug("Bind to port %s on %s.", strport, ntop); + len = sizeof(socksize); + getsockopt(listen_sock, SOL_SOCKET, SO_RCVBUF, &socksize, &len); + debug("Server TCP RWIN socket size: %d", socksize); + debug("HPN Buffer Size: %d", options.hpn_buffer_size); + /* Bind the socket to the desired port. */ if (bind(listen_sock, ai->ai_addr, ai->ai_addrlen) < 0) { error("Bind to port %s on %s failed: %.200s.", strport, ntop, strerror(errno)); close(listen_sock); continue; } listen_socks[num_listen_socks] = listen_sock; num_listen_socks++; /* Start listening on the port. */ if (listen(listen_sock, SSH_LISTEN_BACKLOG) < 0) fatal("listen on [%s]:%s: %.100s", ntop, strport, strerror(errno)); logit("Server listening on %s port %s.", ntop, strport); } freeaddrinfo(options.listen_addrs); if (!num_listen_socks) fatal("Cannot bind any address."); } /* * The main TCP accept loop. Note that, for the non-debug case, returns * from this function are in a forked subprocess. */ static void server_accept_loop(int *sock_in, int *sock_out, int *newsock, int *config_s) { fd_set *fdset; int i, j, ret, maxfd; int key_used = 0, startups = 0; int startup_p[2] = { -1 , -1 }; struct sockaddr_storage from; socklen_t fromlen; pid_t pid; /* setup fd set for accept */ fdset = NULL; maxfd = 0; for (i = 0; i < num_listen_socks; i++) if (listen_socks[i] > maxfd) maxfd = listen_socks[i]; /* pipes connected to unauthenticated childs */ startup_pipes = xcalloc(options.max_startups, sizeof(int)); for (i = 0; i < options.max_startups; i++) startup_pipes[i] = -1; /* * Stay listening for connections until the system crashes or * the daemon is killed with a signal. */ for (;;) { if (received_sighup) sighup_restart(); if (fdset != NULL) xfree(fdset); fdset = (fd_set *)xcalloc(howmany(maxfd + 1, NFDBITS), sizeof(fd_mask)); for (i = 0; i < num_listen_socks; i++) FD_SET(listen_socks[i], fdset); for (i = 0; i < options.max_startups; i++) if (startup_pipes[i] != -1) FD_SET(startup_pipes[i], fdset); /* Wait in select until there is a connection. */ ret = select(maxfd+1, fdset, NULL, NULL, NULL); if (ret < 0 && errno != EINTR) error("select: %.100s", strerror(errno)); if (received_sigterm) { logit("Received signal %d; terminating.", (int) received_sigterm); close_listen_socks(); unlink(options.pid_file); exit(255); } if (key_used && key_do_regen) { generate_ephemeral_server_key(); key_used = 0; key_do_regen = 0; } if (ret < 0) continue; for (i = 0; i < options.max_startups; i++) if (startup_pipes[i] != -1 && FD_ISSET(startup_pipes[i], fdset)) { /* * the read end of the pipe is ready * if the child has closed the pipe * after successful authentication * or if the child has died */ close(startup_pipes[i]); startup_pipes[i] = -1; startups--; } for (i = 0; i < num_listen_socks; i++) { if (!FD_ISSET(listen_socks[i], fdset)) continue; fromlen = sizeof(from); *newsock = accept(listen_socks[i], (struct sockaddr *)&from, &fromlen); if (*newsock < 0) { if (errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK) error("accept: %.100s", strerror(errno)); continue; } if (unset_nonblock(*newsock) == -1) { close(*newsock); continue; } if (drop_connection(startups) == 1) { debug("drop connection #%d", startups); close(*newsock); continue; } if (pipe(startup_p) == -1) { close(*newsock); continue; } if (rexec_flag && socketpair(AF_UNIX, SOCK_STREAM, 0, config_s) == -1) { error("reexec socketpair: %s", strerror(errno)); close(*newsock); close(startup_p[0]); close(startup_p[1]); continue; } for (j = 0; j < options.max_startups; j++) if (startup_pipes[j] == -1) { startup_pipes[j] = startup_p[0]; if (maxfd < startup_p[0]) maxfd = startup_p[0]; startups++; break; } /* * Got connection. Fork a child to handle it, unless * we are in debugging mode. */ if (debug_flag) { /* * In debugging mode. Close the listening * socket, and start processing the * connection without forking. */ debug("Server will not fork when running in debugging mode."); close_listen_socks(); *sock_in = *newsock; *sock_out = *newsock; close(startup_p[0]); close(startup_p[1]); startup_pipe = -1; pid = getpid(); if (rexec_flag) { send_rexec_state(config_s[0], &cfg); close(config_s[0]); } break; } /* * Normal production daemon. Fork, and have * the child process the connection. The * parent continues listening. */ platform_pre_fork(); if ((pid = fork()) == 0) { /* * Child. Close the listening and * max_startup sockets. Start using * the accepted socket. Reinitialize * logging (since our pid has changed). * We break out of the loop to handle * the connection. */ platform_post_fork_child(); startup_pipe = startup_p[1]; close_startup_pipes(); close_listen_socks(); *sock_in = *newsock; *sock_out = *newsock; log_init(__progname, options.log_level, options.log_facility, log_stderr); if (rexec_flag) close(config_s[0]); break; } /* Parent. Stay in the loop. */ platform_post_fork_parent(pid); if (pid < 0) error("fork: %.100s", strerror(errno)); else debug("Forked child %ld.", (long)pid); close(startup_p[1]); if (rexec_flag) { send_rexec_state(config_s[0], &cfg); close(config_s[0]); close(config_s[1]); } /* * Mark that the key has been used (it * was "given" to the child). */ if ((options.protocol & SSH_PROTO_1) && key_used == 0) { /* Schedule server key regeneration alarm. */ signal(SIGALRM, key_regeneration_alarm); alarm(options.key_regeneration_time); key_used = 1; } close(*newsock); /* * Ensure that our random state differs * from that of the child */ arc4random_stir(); } /* child process check (or debug mode) */ if (num_listen_socks < 0) break; } } /* * Main program for the daemon. */ int main(int ac, char **av) { extern char *optarg; extern int optind; int opt, i, j, on = 1; int sock_in = -1, sock_out = -1, newsock = -1; const char *remote_ip; char *test_user = NULL, *test_host = NULL, *test_addr = NULL; int remote_port; char *line, *p, *cp; int config_s[2] = { -1 , -1 }; u_int64_t ibytes, obytes; mode_t new_umask; Key *key; Authctxt *authctxt; #ifdef HAVE_SECUREWARE (void)set_auth_parameters(ac, av); #endif __progname = ssh_get_progname(av[0]); init_rng(); /* Save argv. Duplicate so setproctitle emulation doesn't clobber it */ saved_argc = ac; rexec_argc = ac; saved_argv = xcalloc(ac + 1, sizeof(*saved_argv)); for (i = 0; i < ac; i++) saved_argv[i] = xstrdup(av[i]); saved_argv[i] = NULL; #ifndef HAVE_SETPROCTITLE /* Prepare for later setproctitle emulation */ compat_init_setproctitle(ac, av); av = saved_argv; #endif if (geteuid() == 0 && setgroups(0, NULL) == -1) debug("setgroups(): %.200s", strerror(errno)); /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */ sanitise_stdfd(); /* Initialize configuration options to their default values. */ initialize_server_options(&options); /* Parse command-line arguments. */ while ((opt = getopt(ac, av, "f:p:b:k:h:g:u:o:C:dDeiqrtQRT46")) != -1) { switch (opt) { case '4': options.address_family = AF_INET; break; case '6': options.address_family = AF_INET6; break; case 'f': config_file_name = optarg; break; case 'c': if (options.num_host_cert_files >= MAX_HOSTCERTS) { fprintf(stderr, "too many host certificates.\n"); exit(1); } options.host_cert_files[options.num_host_cert_files++] = derelativise_path(optarg); break; case 'd': if (debug_flag == 0) { debug_flag = 1; options.log_level = SYSLOG_LEVEL_DEBUG1; } else if (options.log_level < SYSLOG_LEVEL_DEBUG3) options.log_level++; break; case 'D': no_daemon_flag = 1; break; case 'e': log_stderr = 1; break; case 'i': inetd_flag = 1; break; case 'r': rexec_flag = 0; break; case 'R': rexeced_flag = 1; inetd_flag = 1; break; case 'Q': /* ignored */ break; case 'q': options.log_level = SYSLOG_LEVEL_QUIET; break; case 'b': options.server_key_bits = (int)strtonum(optarg, 256, 32768, NULL); break; case 'p': options.ports_from_cmdline = 1; if (options.num_ports >= MAX_PORTS) { fprintf(stderr, "too many ports.\n"); exit(1); } options.ports[options.num_ports++] = a2port(optarg); if (options.ports[options.num_ports-1] <= 0) { fprintf(stderr, "Bad port number.\n"); exit(1); } break; case 'g': if ((options.login_grace_time = convtime(optarg)) == -1) { fprintf(stderr, "Invalid login grace time.\n"); exit(1); } break; case 'k': if ((options.key_regeneration_time = convtime(optarg)) == -1) { fprintf(stderr, "Invalid key regeneration interval.\n"); exit(1); } break; case 'h': if (options.num_host_key_files >= MAX_HOSTKEYS) { fprintf(stderr, "too many host keys.\n"); exit(1); } options.host_key_files[options.num_host_key_files++] = derelativise_path(optarg); break; case 't': test_flag = 1; break; case 'T': test_flag = 2; break; case 'C': cp = optarg; while ((p = strsep(&cp, ",")) && *p != '\0') { if (strncmp(p, "addr=", 5) == 0) test_addr = xstrdup(p + 5); else if (strncmp(p, "host=", 5) == 0) test_host = xstrdup(p + 5); else if (strncmp(p, "user=", 5) == 0) test_user = xstrdup(p + 5); else { fprintf(stderr, "Invalid test " "mode specification %s\n", p); exit(1); } } break; case 'u': utmp_len = (u_int)strtonum(optarg, 0, MAXHOSTNAMELEN+1, NULL); if (utmp_len > MAXHOSTNAMELEN) { fprintf(stderr, "Invalid utmp length.\n"); exit(1); } break; case 'o': line = xstrdup(optarg); if (process_server_config_line(&options, line, "command-line", 0, NULL, NULL, NULL, NULL) != 0) exit(1); xfree(line); break; case '?': default: usage(); break; } } if (rexeced_flag || inetd_flag) rexec_flag = 0; if (!test_flag && (rexec_flag && (av[0] == NULL || *av[0] != '/'))) fatal("sshd re-exec requires execution with an absolute path"); if (rexeced_flag) closefrom(REEXEC_MIN_FREE_FD); else closefrom(REEXEC_DEVCRYPTO_RESERVED_FD); OpenSSL_add_all_algorithms(); /* * Force logging to stderr until we have loaded the private host * key (unless started from inetd) */ log_init(__progname, options.log_level == SYSLOG_LEVEL_NOT_SET ? SYSLOG_LEVEL_INFO : options.log_level, options.log_facility == SYSLOG_FACILITY_NOT_SET ? SYSLOG_FACILITY_AUTH : options.log_facility, log_stderr || !inetd_flag); /* * Unset KRB5CCNAME, otherwise the user's session may inherit it from * root's environment */ if (getenv("KRB5CCNAME") != NULL) unsetenv("KRB5CCNAME"); #ifdef _UNICOS /* Cray can define user privs drop all privs now! * Not needed on PRIV_SU systems! */ drop_cray_privs(); #endif sensitive_data.server_key = NULL; sensitive_data.ssh1_host_key = NULL; sensitive_data.have_ssh1_key = 0; sensitive_data.have_ssh2_key = 0; /* * If we're doing an extended config test, make sure we have all of * the parameters we need. If we're not doing an extended test, * do not silently ignore connection test params. */ if (test_flag >= 2 && (test_user != NULL || test_host != NULL || test_addr != NULL) && (test_user == NULL || test_host == NULL || test_addr == NULL)) fatal("user, host and addr are all required when testing " "Match configs"); if (test_flag < 2 && (test_user != NULL || test_host != NULL || test_addr != NULL)) fatal("Config test connection parameter (-C) provided without " "test mode (-T)"); /* Fetch our configuration */ buffer_init(&cfg); if (rexeced_flag) recv_rexec_state(REEXEC_CONFIG_PASS_FD, &cfg); else load_server_config(config_file_name, &cfg); parse_server_config(&options, rexeced_flag ? "rexec" : config_file_name, &cfg, NULL, NULL, NULL); seed_rng(); /* Fill in default values for those options not explicitly set. */ fill_default_server_options(&options); /* challenge-response is implemented via keyboard interactive */ if (options.challenge_response_authentication) options.kbd_interactive_authentication = 1; /* set default channel AF */ channel_set_af(options.address_family); /* Check that there are no remaining arguments. */ if (optind < ac) { fprintf(stderr, "Extra argument %s.\n", av[optind]); exit(1); } debug("sshd version %.100s", SSH_RELEASE); /* Store privilege separation user for later use if required. */ if ((privsep_pw = getpwnam(SSH_PRIVSEP_USER)) == NULL) { if (use_privsep || options.kerberos_authentication) fatal("Privilege separation user %s does not exist", SSH_PRIVSEP_USER); } else { memset(privsep_pw->pw_passwd, 0, strlen(privsep_pw->pw_passwd)); privsep_pw = pwcopy(privsep_pw); xfree(privsep_pw->pw_passwd); privsep_pw->pw_passwd = xstrdup("*"); } endpwent(); /* load private host keys */ sensitive_data.host_keys = xcalloc(options.num_host_key_files, sizeof(Key *)); for (i = 0; i < options.num_host_key_files; i++) sensitive_data.host_keys[i] = NULL; for (i = 0; i < options.num_host_key_files; i++) { key = key_load_private(options.host_key_files[i], "", NULL); sensitive_data.host_keys[i] = key; if (key == NULL) { error("Could not load host key: %s", options.host_key_files[i]); sensitive_data.host_keys[i] = NULL; continue; } switch (key->type) { case KEY_RSA1: sensitive_data.ssh1_host_key = key; sensitive_data.have_ssh1_key = 1; break; case KEY_RSA: case KEY_DSA: case KEY_ECDSA: sensitive_data.have_ssh2_key = 1; break; } debug("private host key: #%d type %d %s", i, key->type, key_type(key)); } if ((options.protocol & SSH_PROTO_1) && !sensitive_data.have_ssh1_key) { logit("Disabling protocol version 1. Could not load host key"); options.protocol &= ~SSH_PROTO_1; } if ((options.protocol & SSH_PROTO_2) && !sensitive_data.have_ssh2_key) { logit("Disabling protocol version 2. Could not load host key"); options.protocol &= ~SSH_PROTO_2; } if (!(options.protocol & (SSH_PROTO_1|SSH_PROTO_2))) { logit("sshd: no hostkeys available -- exiting."); exit(1); } /* * Load certificates. They are stored in an array at identical * indices to the public keys that they relate to. */ sensitive_data.host_certificates = xcalloc(options.num_host_key_files, sizeof(Key *)); for (i = 0; i < options.num_host_key_files; i++) sensitive_data.host_certificates[i] = NULL; for (i = 0; i < options.num_host_cert_files; i++) { key = key_load_public(options.host_cert_files[i], NULL); if (key == NULL) { error("Could not load host certificate: %s", options.host_cert_files[i]); continue; } if (!key_is_cert(key)) { error("Certificate file is not a certificate: %s", options.host_cert_files[i]); key_free(key); continue; } /* Find matching private key */ for (j = 0; j < options.num_host_key_files; j++) { if (key_equal_public(key, sensitive_data.host_keys[j])) { sensitive_data.host_certificates[j] = key; break; } } if (j >= options.num_host_key_files) { error("No matching private key for certificate: %s", options.host_cert_files[i]); key_free(key); continue; } sensitive_data.host_certificates[j] = key; debug("host certificate: #%d type %d %s", j, key->type, key_type(key)); } /* Check certain values for sanity. */ if (options.protocol & SSH_PROTO_1) { if (options.server_key_bits < 512 || options.server_key_bits > 32768) { fprintf(stderr, "Bad server key size.\n"); exit(1); } /* * Check that server and host key lengths differ sufficiently. This * is necessary to make double encryption work with rsaref. Oh, I * hate software patents. I dont know if this can go? Niels */ if (options.server_key_bits > BN_num_bits(sensitive_data.ssh1_host_key->rsa->n) - SSH_KEY_BITS_RESERVED && options.server_key_bits < BN_num_bits(sensitive_data.ssh1_host_key->rsa->n) + SSH_KEY_BITS_RESERVED) { options.server_key_bits = BN_num_bits(sensitive_data.ssh1_host_key->rsa->n) + SSH_KEY_BITS_RESERVED; debug("Forcing server key to %d bits to make it differ from host key.", options.server_key_bits); } } if (use_privsep) { struct stat st; if ((stat(_PATH_PRIVSEP_CHROOT_DIR, &st) == -1) || (S_ISDIR(st.st_mode) == 0)) fatal("Missing privilege separation directory: %s", _PATH_PRIVSEP_CHROOT_DIR); #ifdef HAVE_CYGWIN if (check_ntsec(_PATH_PRIVSEP_CHROOT_DIR) && (st.st_uid != getuid () || (st.st_mode & (S_IWGRP|S_IWOTH)) != 0)) #else if (st.st_uid != 0 || (st.st_mode & (S_IWGRP|S_IWOTH)) != 0) #endif fatal("%s must be owned by root and not group or " "world-writable.", _PATH_PRIVSEP_CHROOT_DIR); } if (test_flag > 1) { if (test_user != NULL && test_addr != NULL && test_host != NULL) parse_server_match_config(&options, test_user, test_host, test_addr); dump_config(&options); } /* Configuration looks good, so exit if in test mode. */ if (test_flag) exit(0); /* * Clear out any supplemental groups we may have inherited. This * prevents inadvertent creation of files with bad modes (in the * portable version at least, it's certainly possible for PAM * to create a file, and we can't control the code in every * module which might be used). */ if (setgroups(0, NULL) < 0) debug("setgroups() failed: %.200s", strerror(errno)); if (rexec_flag) { rexec_argv = xcalloc(rexec_argc + 2, sizeof(char *)); for (i = 0; i < rexec_argc; i++) { debug("rexec_argv[%d]='%s'", i, saved_argv[i]); rexec_argv[i] = saved_argv[i]; } rexec_argv[rexec_argc] = "-R"; rexec_argv[rexec_argc + 1] = NULL; } /* Ensure that umask disallows at least group and world write */ new_umask = umask(0077) | 0022; (void) umask(new_umask); /* Initialize the log (it is reinitialized below in case we forked). */ if (debug_flag && (!inetd_flag || rexeced_flag)) log_stderr = 1; log_init(__progname, options.log_level, options.log_facility, log_stderr); /* * If not in debugging mode, and not started from inetd, disconnect * from the controlling terminal, and fork. The original process * exits. */ if (!(debug_flag || inetd_flag || no_daemon_flag)) { #ifdef TIOCNOTTY int fd; #endif /* TIOCNOTTY */ if (daemon(0, 0) < 0) fatal("daemon() failed: %.200s", strerror(errno)); /* Disconnect from the controlling tty. */ #ifdef TIOCNOTTY fd = open(_PATH_TTY, O_RDWR | O_NOCTTY); if (fd >= 0) { (void) ioctl(fd, TIOCNOTTY, NULL); close(fd); } #endif /* TIOCNOTTY */ } /* Reinitialize the log (because of the fork above). */ log_init(__progname, options.log_level, options.log_facility, log_stderr); /* Avoid killing the process in high-pressure swapping environments. */ if (!inetd_flag && madvise(NULL, 0, MADV_PROTECT) != 0) debug("madvise(): %.200s", strerror(errno)); /* Initialize the random number generator. */ arc4random_stir(); /* Chdir to the root directory so that the current disk can be unmounted if desired. */ chdir("/"); /* ignore SIGPIPE */ signal(SIGPIPE, SIG_IGN); /* Get a connection, either from inetd or a listening TCP socket */ if (inetd_flag) { server_accept_inetd(&sock_in, &sock_out); } else { platform_pre_listen(); server_listen(); if (options.protocol & SSH_PROTO_1) generate_ephemeral_server_key(); signal(SIGHUP, sighup_handler); signal(SIGCHLD, main_sigchld_handler); signal(SIGTERM, sigterm_handler); signal(SIGQUIT, sigterm_handler); /* * Write out the pid file after the sigterm handler * is setup and the listen sockets are bound */ if (!debug_flag) { FILE *f = fopen(options.pid_file, "w"); if (f == NULL) { error("Couldn't create pid file \"%s\": %s", options.pid_file, strerror(errno)); } else { fprintf(f, "%ld\n", (long) getpid()); fclose(f); } } /* Accept a connection and return in a forked child */ server_accept_loop(&sock_in, &sock_out, &newsock, config_s); } /* This is the child processing a new connection. */ setproctitle("%s", "[accepted]"); /* * Create a new session and process group since the 4.4BSD * setlogin() affects the entire process group. We don't * want the child to be able to affect the parent. */ #if !defined(SSHD_ACQUIRES_CTTY) /* * If setsid is called, on some platforms sshd will later acquire a * controlling terminal which will result in "could not set * controlling tty" errors. */ if (!debug_flag && !inetd_flag && setsid() < 0) error("setsid: %.100s", strerror(errno)); #endif if (rexec_flag) { int fd; debug("rexec start in %d out %d newsock %d pipe %d sock %d", sock_in, sock_out, newsock, startup_pipe, config_s[0]); dup2(newsock, STDIN_FILENO); dup2(STDIN_FILENO, STDOUT_FILENO); if (startup_pipe == -1) close(REEXEC_STARTUP_PIPE_FD); else dup2(startup_pipe, REEXEC_STARTUP_PIPE_FD); dup2(config_s[1], REEXEC_CONFIG_PASS_FD); close(config_s[1]); if (startup_pipe != -1) close(startup_pipe); execv(rexec_argv[0], rexec_argv); /* Reexec has failed, fall back and continue */ error("rexec of %s failed: %s", rexec_argv[0], strerror(errno)); recv_rexec_state(REEXEC_CONFIG_PASS_FD, NULL); log_init(__progname, options.log_level, options.log_facility, log_stderr); /* Clean up fds */ startup_pipe = REEXEC_STARTUP_PIPE_FD; close(config_s[1]); close(REEXEC_CONFIG_PASS_FD); newsock = sock_out = sock_in = dup(STDIN_FILENO); if ((fd = open(_PATH_DEVNULL, O_RDWR, 0)) != -1) { dup2(fd, STDIN_FILENO); dup2(fd, STDOUT_FILENO); if (fd > STDERR_FILENO) close(fd); } debug("rexec cleanup in %d out %d newsock %d pipe %d sock %d", sock_in, sock_out, newsock, startup_pipe, config_s[0]); } /* Executed child processes don't need these. */ fcntl(sock_out, F_SETFD, FD_CLOEXEC); fcntl(sock_in, F_SETFD, FD_CLOEXEC); /* * Disable the key regeneration alarm. We will not regenerate the * key since we are no longer in a position to give it to anyone. We * will not restart on SIGHUP since it no longer makes sense. */ alarm(0); signal(SIGALRM, SIG_DFL); signal(SIGHUP, SIG_DFL); signal(SIGTERM, SIG_DFL); signal(SIGQUIT, SIG_DFL); signal(SIGCHLD, SIG_DFL); signal(SIGINT, SIG_DFL); #ifdef __FreeBSD__ /* * Initialize the resolver. This may not happen automatically * before privsep chroot(). */ if ((_res.options & RES_INIT) == 0) { debug("res_init()"); res_init(); } #ifdef GSSAPI /* * Force GSS-API to parse its configuration and load any * mechanism plugins. */ { gss_OID_set mechs; OM_uint32 minor_status; gss_indicate_mechs(&minor_status, &mechs); gss_release_oid_set(&minor_status, &mechs); } #endif #endif /* * Register our connection. This turns encryption off because we do * not have a key. */ packet_set_connection(sock_in, sock_out); packet_set_server(); /* Set SO_KEEPALIVE if requested. */ if (options.tcp_keep_alive && packet_connection_is_on_socket() && setsockopt(sock_in, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on)) < 0) error("setsockopt SO_KEEPALIVE: %.100s", strerror(errno)); if ((remote_port = get_remote_port()) < 0) { debug("get_remote_port failed"); cleanup_exit(255); } /* * We use get_canonical_hostname with usedns = 0 instead of * get_remote_ipaddr here so IP options will be checked. */ (void) get_canonical_hostname(0); /* * The rest of the code depends on the fact that * get_remote_ipaddr() caches the remote ip, even if * the socket goes away. */ remote_ip = get_remote_ipaddr(); #ifdef SSH_AUDIT_EVENTS audit_connection_from(remote_ip, remote_port); #endif #ifdef LIBWRAP allow_severity = options.log_facility|LOG_INFO; deny_severity = options.log_facility|LOG_WARNING; /* Check whether logins are denied from this host. */ if (packet_connection_is_on_socket()) { struct request_info req; request_init(&req, RQ_DAEMON, __progname, RQ_FILE, sock_in, 0); fromhost(&req); if (!hosts_access(&req)) { debug("Connection refused by tcp wrapper"); refuse(&req); /* NOTREACHED */ fatal("libwrap refuse returns"); } } #endif /* LIBWRAP */ /* Log the connection. */ verbose("Connection from %.500s port %d", remote_ip, remote_port); + /* Set HPN options for the child. */ + channel_set_hpn(options.hpn_disabled, options.hpn_buffer_size); + /* * We don't want to listen forever unless the other side * successfully authenticates itself. So we set up an alarm which is * cleared after successful authentication. A limit of zero * indicates no limit. Note that we don't set the alarm in debugging * mode; it is just annoying to have the server exit just when you * are about to discover the bug. */ signal(SIGALRM, grace_alarm_handler); if (!debug_flag) alarm(options.login_grace_time); sshd_exchange_identification(sock_in, sock_out); /* In inetd mode, generate ephemeral key only for proto 1 connections */ if (!compat20 && inetd_flag && sensitive_data.server_key == NULL) generate_ephemeral_server_key(); packet_set_nonblocking(); /* allocate authentication context */ authctxt = xcalloc(1, sizeof(*authctxt)); authctxt->loginmsg = &loginmsg; /* XXX global for cleanup, access from other modules */ the_authctxt = authctxt; /* prepare buffer to collect messages to display to user after login */ buffer_init(&loginmsg); auth_debug_reset(); if (use_privsep) if (privsep_preauth(authctxt) == 1) goto authenticated; /* perform the key exchange */ /* authenticate user and start session */ if (compat20) { do_ssh2_kex(); do_authentication2(authctxt); } else { do_ssh1_kex(); do_authentication(authctxt); } /* * If we use privilege separation, the unprivileged child transfers * the current keystate and exits */ if (use_privsep) { mm_send_keystate(pmonitor); exit(0); } authenticated: /* * Cancel the alarm we set to limit the time taken for * authentication. */ alarm(0); signal(SIGALRM, SIG_DFL); authctxt->authenticated = 1; if (startup_pipe != -1) { close(startup_pipe); startup_pipe = -1; } #ifdef SSH_AUDIT_EVENTS audit_event(SSH_AUTH_SUCCESS); #endif #ifdef GSSAPI if (options.gss_authentication) { temporarily_use_uid(authctxt->pw); ssh_gssapi_storecreds(); restore_uid(); } #endif #ifdef USE_PAM if (options.use_pam) { do_pam_setcred(1); do_pam_session(); } #endif /* * In privilege separation, we fork another child and prepare * file descriptor passing. */ if (use_privsep) { privsep_postauth(authctxt); /* the monitor process [priv] will not return */ if (!compat20) destroy_sensitive_data(); } packet_set_timeout(options.client_alive_interval, options.client_alive_count_max); /* Start session. */ do_authenticated(authctxt); /* The connection has been terminated. */ packet_get_state(MODE_IN, NULL, NULL, NULL, &ibytes); packet_get_state(MODE_OUT, NULL, NULL, NULL, &obytes); verbose("Transferred: sent %llu, received %llu bytes", (unsigned long long)obytes, (unsigned long long)ibytes); verbose("Closing connection to %.500s port %d", remote_ip, remote_port); #ifdef USE_PAM if (options.use_pam) finish_pam(); #endif /* USE_PAM */ #ifdef SSH_AUDIT_EVENTS PRIVSEP(audit_event(SSH_CONNECTION_CLOSE)); #endif packet_close(); if (use_privsep) mm_terminate(); exit(0); } /* * Decrypt session_key_int using our private server key and private host key * (key with larger modulus first). */ int ssh1_session_key(BIGNUM *session_key_int) { int rsafail = 0; if (BN_cmp(sensitive_data.server_key->rsa->n, sensitive_data.ssh1_host_key->rsa->n) > 0) { /* Server key has bigger modulus. */ if (BN_num_bits(sensitive_data.server_key->rsa->n) < BN_num_bits(sensitive_data.ssh1_host_key->rsa->n) + SSH_KEY_BITS_RESERVED) { fatal("do_connection: %s: " "server_key %d < host_key %d + SSH_KEY_BITS_RESERVED %d", get_remote_ipaddr(), BN_num_bits(sensitive_data.server_key->rsa->n), BN_num_bits(sensitive_data.ssh1_host_key->rsa->n), SSH_KEY_BITS_RESERVED); } if (rsa_private_decrypt(session_key_int, session_key_int, sensitive_data.server_key->rsa) <= 0) rsafail++; if (rsa_private_decrypt(session_key_int, session_key_int, sensitive_data.ssh1_host_key->rsa) <= 0) rsafail++; } else { /* Host key has bigger modulus (or they are equal). */ if (BN_num_bits(sensitive_data.ssh1_host_key->rsa->n) < BN_num_bits(sensitive_data.server_key->rsa->n) + SSH_KEY_BITS_RESERVED) { fatal("do_connection: %s: " "host_key %d < server_key %d + SSH_KEY_BITS_RESERVED %d", get_remote_ipaddr(), BN_num_bits(sensitive_data.ssh1_host_key->rsa->n), BN_num_bits(sensitive_data.server_key->rsa->n), SSH_KEY_BITS_RESERVED); } if (rsa_private_decrypt(session_key_int, session_key_int, sensitive_data.ssh1_host_key->rsa) < 0) rsafail++; if (rsa_private_decrypt(session_key_int, session_key_int, sensitive_data.server_key->rsa) < 0) rsafail++; } return (rsafail); } /* * SSH1 key exchange */ static void do_ssh1_kex(void) { int i, len; int rsafail = 0; BIGNUM *session_key_int; u_char session_key[SSH_SESSION_KEY_LENGTH]; u_char cookie[8]; u_int cipher_type, auth_mask, protocol_flags; /* * Generate check bytes that the client must send back in the user * packet in order for it to be accepted; this is used to defy ip * spoofing attacks. Note that this only works against somebody * doing IP spoofing from a remote machine; any machine on the local * network can still see outgoing packets and catch the random * cookie. This only affects rhosts authentication, and this is one * of the reasons why it is inherently insecure. */ arc4random_buf(cookie, sizeof(cookie)); /* * Send our public key. We include in the packet 64 bits of random * data that must be matched in the reply in order to prevent IP * spoofing. */ packet_start(SSH_SMSG_PUBLIC_KEY); for (i = 0; i < 8; i++) packet_put_char(cookie[i]); /* Store our public server RSA key. */ packet_put_int(BN_num_bits(sensitive_data.server_key->rsa->n)); packet_put_bignum(sensitive_data.server_key->rsa->e); packet_put_bignum(sensitive_data.server_key->rsa->n); /* Store our public host RSA key. */ packet_put_int(BN_num_bits(sensitive_data.ssh1_host_key->rsa->n)); packet_put_bignum(sensitive_data.ssh1_host_key->rsa->e); packet_put_bignum(sensitive_data.ssh1_host_key->rsa->n); /* Put protocol flags. */ packet_put_int(SSH_PROTOFLAG_HOST_IN_FWD_OPEN); /* Declare which ciphers we support. */ packet_put_int(cipher_mask_ssh1(0)); /* Declare supported authentication types. */ auth_mask = 0; if (options.rhosts_rsa_authentication) auth_mask |= 1 << SSH_AUTH_RHOSTS_RSA; if (options.rsa_authentication) auth_mask |= 1 << SSH_AUTH_RSA; if (options.challenge_response_authentication == 1) auth_mask |= 1 << SSH_AUTH_TIS; if (options.password_authentication) auth_mask |= 1 << SSH_AUTH_PASSWORD; packet_put_int(auth_mask); /* Send the packet and wait for it to be sent. */ packet_send(); packet_write_wait(); debug("Sent %d bit server key and %d bit host key.", BN_num_bits(sensitive_data.server_key->rsa->n), BN_num_bits(sensitive_data.ssh1_host_key->rsa->n)); /* Read clients reply (cipher type and session key). */ packet_read_expect(SSH_CMSG_SESSION_KEY); /* Get cipher type and check whether we accept this. */ cipher_type = packet_get_char(); if (!(cipher_mask_ssh1(0) & (1 << cipher_type))) packet_disconnect("Warning: client selects unsupported cipher."); /* Get check bytes from the packet. These must match those we sent earlier with the public key packet. */ for (i = 0; i < 8; i++) if (cookie[i] != packet_get_char()) packet_disconnect("IP Spoofing check bytes do not match."); debug("Encryption type: %.200s", cipher_name(cipher_type)); /* Get the encrypted integer. */ if ((session_key_int = BN_new()) == NULL) fatal("do_ssh1_kex: BN_new failed"); packet_get_bignum(session_key_int); protocol_flags = packet_get_int(); packet_set_protocol_flags(protocol_flags); packet_check_eom(); /* Decrypt session_key_int using host/server keys */ rsafail = PRIVSEP(ssh1_session_key(session_key_int)); /* * Extract session key from the decrypted integer. The key is in the * least significant 256 bits of the integer; the first byte of the * key is in the highest bits. */ if (!rsafail) { (void) BN_mask_bits(session_key_int, sizeof(session_key) * 8); len = BN_num_bytes(session_key_int); if (len < 0 || (u_int)len > sizeof(session_key)) { error("do_ssh1_kex: bad session key len from %s: " "session_key_int %d > sizeof(session_key) %lu", get_remote_ipaddr(), len, (u_long)sizeof(session_key)); rsafail++; } else { memset(session_key, 0, sizeof(session_key)); BN_bn2bin(session_key_int, session_key + sizeof(session_key) - len); derive_ssh1_session_id( sensitive_data.ssh1_host_key->rsa->n, sensitive_data.server_key->rsa->n, cookie, session_id); /* * Xor the first 16 bytes of the session key with the * session id. */ for (i = 0; i < 16; i++) session_key[i] ^= session_id[i]; } } if (rsafail) { int bytes = BN_num_bytes(session_key_int); u_char *buf = xmalloc(bytes); MD5_CTX md; logit("do_connection: generating a fake encryption key"); BN_bn2bin(session_key_int, buf); MD5_Init(&md); MD5_Update(&md, buf, bytes); MD5_Update(&md, sensitive_data.ssh1_cookie, SSH_SESSION_KEY_LENGTH); MD5_Final(session_key, &md); MD5_Init(&md); MD5_Update(&md, session_key, 16); MD5_Update(&md, buf, bytes); MD5_Update(&md, sensitive_data.ssh1_cookie, SSH_SESSION_KEY_LENGTH); MD5_Final(session_key + 16, &md); memset(buf, 0, bytes); xfree(buf); for (i = 0; i < 16; i++) session_id[i] = session_key[i] ^ session_key[i + 16]; } /* Destroy the private and public keys. No longer. */ destroy_sensitive_data(); if (use_privsep) mm_ssh1_session_id(session_id); /* Destroy the decrypted integer. It is no longer needed. */ BN_clear_free(session_key_int); /* Set the session key. From this on all communications will be encrypted. */ packet_set_encryption_key(session_key, SSH_SESSION_KEY_LENGTH, cipher_type); /* Destroy our copy of the session key. It is no longer needed. */ memset(session_key, 0, sizeof(session_key)); debug("Received session key; encryption turned on."); /* Send an acknowledgment packet. Note that this packet is sent encrypted. */ packet_start(SSH_SMSG_SUCCESS); packet_send(); packet_write_wait(); } /* * SSH2 key exchange: diffie-hellman-group1-sha1 */ static void do_ssh2_kex(void) { Kex *kex; if (options.ciphers != NULL) { myproposal[PROPOSAL_ENC_ALGS_CTOS] = myproposal[PROPOSAL_ENC_ALGS_STOC] = options.ciphers; +#ifdef NONE_CIPHER_ENABLED + } else if (options.none_enabled == 1) { + debug ("WARNING: None cipher enabled"); + myproposal[PROPOSAL_ENC_ALGS_CTOS] = + myproposal[PROPOSAL_ENC_ALGS_STOC] = KEX_ENCRYPT_INCLUDE_NONE; +#endif } myproposal[PROPOSAL_ENC_ALGS_CTOS] = compat_cipher_proposal(myproposal[PROPOSAL_ENC_ALGS_CTOS]); myproposal[PROPOSAL_ENC_ALGS_STOC] = compat_cipher_proposal(myproposal[PROPOSAL_ENC_ALGS_STOC]); if (options.macs != NULL) { myproposal[PROPOSAL_MAC_ALGS_CTOS] = myproposal[PROPOSAL_MAC_ALGS_STOC] = options.macs; } if (options.compression == COMP_NONE) { myproposal[PROPOSAL_COMP_ALGS_CTOS] = myproposal[PROPOSAL_COMP_ALGS_STOC] = "none"; } else if (options.compression == COMP_DELAYED) { myproposal[PROPOSAL_COMP_ALGS_CTOS] = myproposal[PROPOSAL_COMP_ALGS_STOC] = "none,zlib@openssh.com"; } if (options.kex_algorithms != NULL) myproposal[PROPOSAL_KEX_ALGS] = options.kex_algorithms; myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = list_hostkey_types(); /* start key exchange */ kex = kex_setup(myproposal); kex->kex[KEX_DH_GRP1_SHA1] = kexdh_server; kex->kex[KEX_DH_GRP14_SHA1] = kexdh_server; kex->kex[KEX_DH_GEX_SHA1] = kexgex_server; kex->kex[KEX_DH_GEX_SHA256] = kexgex_server; kex->kex[KEX_ECDH_SHA2] = kexecdh_server; kex->server = 1; kex->client_version_string=client_version_string; kex->server_version_string=server_version_string; kex->load_host_public_key=&get_hostkey_public_by_type; kex->load_host_private_key=&get_hostkey_private_by_type; kex->host_key_index=&get_hostkey_index; xxx_kex = kex; dispatch_run(DISPATCH_BLOCK, &kex->done, kex); session_id2 = kex->session_id; session_id2_len = kex->session_id_len; #ifdef DEBUG_KEXDH /* send 1st encrypted/maced/compressed message */ packet_start(SSH2_MSG_IGNORE); packet_put_cstring("markus"); packet_send(); packet_write_wait(); #endif debug("KEX done"); } /* server specific fatal cleanup */ void cleanup_exit(int i) { if (the_authctxt) do_cleanup(the_authctxt); #ifdef SSH_AUDIT_EVENTS /* done after do_cleanup so it can cancel the PAM auth 'thread' */ if (!use_privsep || mm_is_monitor()) audit_event(SSH_CONNECTION_ABANDON); #endif _exit(i); } diff --git a/crypto/openssh/sshd_config b/crypto/openssh/sshd_config index 7858e78c51a6..f5079513bbb6 100644 --- a/crypto/openssh/sshd_config +++ b/crypto/openssh/sshd_config @@ -1,124 +1,136 @@ # $OpenBSD: sshd_config,v 1.82 2010/09/06 17:10:19 naddy Exp $ # $FreeBSD$ # This is the sshd server system-wide configuration file. See # sshd_config(5) for more information. # This sshd was compiled with PATH=/usr/bin:/bin:/usr/sbin:/sbin # The strategy used for options in the default sshd_config shipped with # OpenSSH is to specify options with their default value where # possible, but leave them commented. Uncommented options change a # default value. # Note that some of FreeBSD's defaults differ from OpenBSD's, and # FreeBSD has a few additional options. #VersionAddendum FreeBSD-20110503 #Port 22 #AddressFamily any #ListenAddress 0.0.0.0 #ListenAddress :: # The default requires explicit activation of protocol 1 #Protocol 2 # HostKey for protocol version 1 #HostKey /etc/ssh/ssh_host_key # HostKeys for protocol version 2 #HostKey /etc/ssh/ssh_host_rsa_key #HostKey /etc/ssh/ssh_host_dsa_key #HostKey /etc/ssh/ssh_host_ecdsa_key # Lifetime and size of ephemeral version 1 server key #KeyRegenerationInterval 1h #ServerKeyBits 1024 # Logging # obsoletes QuietMode and FascistLogging #SyslogFacility AUTH #LogLevel INFO # Authentication: #LoginGraceTime 2m #PermitRootLogin no #StrictModes yes #MaxAuthTries 6 #MaxSessions 10 #RSAAuthentication yes #PubkeyAuthentication yes #AuthorizedKeysFile .ssh/authorized_keys # For this to work you will also need host keys in /etc/ssh/ssh_known_hosts #RhostsRSAAuthentication no # similar for protocol version 2 #HostbasedAuthentication no # Change to yes if you don't trust ~/.ssh/known_hosts for # RhostsRSAAuthentication and HostbasedAuthentication #IgnoreUserKnownHosts no # Don't read the user's ~/.rhosts and ~/.shosts files #IgnoreRhosts yes # Change to yes to enable built-in password authentication. #PasswordAuthentication no #PermitEmptyPasswords no # Change to no to disable PAM authentication #ChallengeResponseAuthentication yes # Kerberos options #KerberosAuthentication no #KerberosOrLocalPasswd yes #KerberosTicketCleanup yes #KerberosGetAFSToken no # GSSAPI options #GSSAPIAuthentication no #GSSAPICleanupCredentials yes # Set this to 'no' to disable PAM authentication, account processing, # and session processing. If this is enabled, PAM authentication will # be allowed through the ChallengeResponseAuthentication and # PasswordAuthentication. Depending on your PAM configuration, # PAM authentication via ChallengeResponseAuthentication may bypass # the setting of "PermitRootLogin without-password". # If you just want the PAM account and session checks to run without # PAM authentication, then enable this but set PasswordAuthentication # and ChallengeResponseAuthentication to 'no'. #UsePAM yes #AllowAgentForwarding yes #AllowTcpForwarding yes #GatewayPorts no #X11Forwarding yes #X11DisplayOffset 10 #X11UseLocalhost yes #PrintMotd yes #PrintLastLog yes #TCPKeepAlive yes #UseLogin no #UsePrivilegeSeparation yes #PermitUserEnvironment no #Compression delayed #ClientAliveInterval 0 #ClientAliveCountMax 3 #UseDNS yes #PidFile /var/run/sshd.pid #MaxStartups 10 #PermitTunnel no #ChrootDirectory none # no default banner path #Banner none # override default of no subsystems Subsystem sftp /usr/libexec/sftp-server +# Disable HPN tuning improvements. +#HPNDisabled no + +# Buffer size for HPN to non-HPN connections. +#HPNBufferSize 2048 + +# TCP receive socket buffer polling for HPN. Disable on non autotuning kernels. +#TcpRcvBufPoll yes + +# Allow the use of the NONE cipher. +#NoneEnabled no + # Example of overriding settings on a per-user basis #Match User anoncvs # X11Forwarding no # AllowTcpForwarding no # ForceCommand cvs server diff --git a/crypto/openssh/version.c b/crypto/openssh/version.c index 88361ae651a3..3cb4b7a13aba 100644 --- a/crypto/openssh/version.c +++ b/crypto/openssh/version.c @@ -1,62 +1,64 @@ /*- * Copyright (c) 2001 Brian Fundakowski Feldman * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * */ #include "includes.h" __RCSID("$FreeBSD$"); #include #include "version.h" #include "xmalloc.h" static char *version = NULL; const char * ssh_version_get(void) { if (version == NULL) - version = xstrdup(SSH_VERSION_BASE " " SSH_VERSION_ADDENDUM); + version = xstrdup(SSH_VERSION); return (version); } void ssh_version_set_addendum(const char *add) { char *newvers; size_t size; if (add != NULL) { - size = strlen(SSH_VERSION_BASE) + 1 + strlen(add) + 1; + size = strlen(SSH_VERSION_BASE) + strlen(SSH_VERSION_HPN) + 1 + + strlen(add) + 1; newvers = xmalloc(size); - snprintf(newvers, size, "%s %s", SSH_VERSION_BASE, add); + snprintf(newvers, size, "%s %s", SSH_VERSION_BASE, + SSH_VERSION_HPN, add); } else { - newvers = xstrdup(SSH_VERSION_BASE); + newvers = xstrdup(SSH_VERSION_BASE SSH_VERSION_HPN); } if (version != NULL) xfree(version); version = newvers; } diff --git a/crypto/openssh/version.h b/crypto/openssh/version.h index 00dbdf3f692c..a15489290926 100644 --- a/crypto/openssh/version.h +++ b/crypto/openssh/version.h @@ -1,13 +1,14 @@ /* $OpenBSD: version.h,v 1.61 2011/02/04 00:44:43 djm Exp $ */ /* $FreeBSD$ */ #ifndef SSH_VERSION -#define SSH_VERSION (ssh_version_get()) -#define SSH_RELEASE (ssh_version_get()) #define SSH_VERSION_BASE "OpenSSH_5.8p2" #define SSH_VERSION_ADDENDUM "FreeBSD-20110503" +#define SSH_VERSION_HPN "_hpn13v11" +#define SSH_VERSION SSH_VERSION_BASE SSH_VERSION_HPN " " SSH_VERSION_ADDENDUM +#define SSH_RELEASE (ssh_version_get()) const char *ssh_version_get(void); void ssh_version_set_addendum(const char *); #endif /* SSH_VERSION */