Import OpenSSH-5.6p1.
authorPeter Avalos <pavalos@dragonflybsd.org>
Wed, 29 Sep 2010 01:03:24 +0000 (15:03 -1000)
committerPeter Avalos <pavalos@dragonflybsd.org>
Wed, 29 Sep 2010 01:03:24 +0000 (15:03 -1000)
117 files changed:
crypto/openssh/PROTOCOL
crypto/openssh/PROTOCOL.agent
crypto/openssh/PROTOCOL.certkeys [new file with mode: 0644]
crypto/openssh/PROTOCOL.mux [new file with mode: 0644]
crypto/openssh/README
crypto/openssh/README.DELETED
crypto/openssh/addrmatch.c
crypto/openssh/auth-krb5.c
crypto/openssh/auth-options.c
crypto/openssh/auth-options.h
crypto/openssh/auth-rh-rsa.c
crypto/openssh/auth-rhosts.c
crypto/openssh/auth-rsa.c
crypto/openssh/auth.c
crypto/openssh/auth.h
crypto/openssh/auth1.c
crypto/openssh/auth2-hostbased.c
crypto/openssh/auth2-none.c
crypto/openssh/auth2-pubkey.c
crypto/openssh/authfd.c
crypto/openssh/authfd.h
crypto/openssh/authfile.c
crypto/openssh/authfile.h
crypto/openssh/bufaux.c
crypto/openssh/buffer.c
crypto/openssh/buffer.h
crypto/openssh/canohost.c
crypto/openssh/channels.c
crypto/openssh/channels.h
crypto/openssh/clientloop.c
crypto/openssh/clientloop.h
crypto/openssh/defines.h
crypto/openssh/dh.c
crypto/openssh/dns.c
crypto/openssh/dns.h
crypto/openssh/hostfile.c
crypto/openssh/hostfile.h
crypto/openssh/jpake.c
crypto/openssh/kex.c
crypto/openssh/kex.h
crypto/openssh/kexdhs.c
crypto/openssh/kexgexs.c
crypto/openssh/key.c
crypto/openssh/key.h
crypto/openssh/loginrec.c
crypto/openssh/loginrec.h
crypto/openssh/match.h
crypto/openssh/misc.c
crypto/openssh/misc.h
crypto/openssh/monitor.c
crypto/openssh/monitor_fdpass.c
crypto/openssh/monitor_wrap.c
crypto/openssh/mux.c
crypto/openssh/myproposal.h
crypto/openssh/nchan.c
crypto/openssh/openbsd-compat/openbsd-compat.h
crypto/openssh/openbsd-compat/openssl-compat.h
crypto/openssh/openbsd-compat/port-aix.h
crypto/openssh/openbsd-compat/port-linux.h
crypto/openssh/openbsd-compat/port-tun.c
crypto/openssh/packet.c
crypto/openssh/pathnames.h
crypto/openssh/pkcs11.h [new file with mode: 0644]
crypto/openssh/platform.c
crypto/openssh/platform.h
crypto/openssh/readconf.c
crypto/openssh/readconf.h
crypto/openssh/roaming.h
crypto/openssh/roaming_client.c [new file with mode: 0644]
crypto/openssh/roaming_common.c
crypto/openssh/roaming_serv.c [copied from crypto/openssh/platform.h with 71% similarity]
crypto/openssh/scard-opensc.c [deleted file]
crypto/openssh/scard.c [deleted file]
crypto/openssh/scp.1
crypto/openssh/scp.c
crypto/openssh/servconf.c
crypto/openssh/servconf.h
crypto/openssh/session.c
crypto/openssh/sftp-client.c
crypto/openssh/sftp-client.h
crypto/openssh/sftp-common.c
crypto/openssh/sftp-common.h
crypto/openssh/sftp-server.8
crypto/openssh/sftp-server.c
crypto/openssh/sftp.1
crypto/openssh/sftp.c
crypto/openssh/ssh-add.1
crypto/openssh/ssh-add.c
crypto/openssh/ssh-agent.1
crypto/openssh/ssh-agent.c
crypto/openssh/ssh-dss.c
crypto/openssh/ssh-keygen.1
crypto/openssh/ssh-keygen.c
crypto/openssh/ssh-keyscan.1
crypto/openssh/ssh-keyscan.c
crypto/openssh/ssh-keysign.8
crypto/openssh/ssh-keysign.c
crypto/openssh/ssh-pkcs11-client.c [new file with mode: 0644]
crypto/openssh/ssh-pkcs11-helper.8 [new file with mode: 0644]
crypto/openssh/ssh-pkcs11-helper.c [new file with mode: 0644]
crypto/openssh/ssh-pkcs11.c [new file with mode: 0644]
crypto/openssh/ssh-pkcs11.h [copied from crypto/openssh/platform.h with 74% similarity]
crypto/openssh/ssh-rsa.c
crypto/openssh/ssh.1
crypto/openssh/ssh.c
crypto/openssh/ssh.h
crypto/openssh/ssh2.h
crypto/openssh/ssh_config
crypto/openssh/ssh_config.5
crypto/openssh/sshconnect.c
crypto/openssh/sshconnect2.c
crypto/openssh/sshd.8
crypto/openssh/sshd.c
crypto/openssh/sshd_config
crypto/openssh/sshd_config.5
crypto/openssh/sshpty.h
crypto/openssh/sshtty.c

index 5aada63..5fc31ea 100644 (file)
@@ -6,8 +6,8 @@ filexfer protocol described in:
 
 http://www.openssh.com/txt/draft-ietf-secsh-filexfer-02.txt
 
-Features from newer versions of the draft are not supported, unless
-explicitly implemented as extensions described below.
+Newer versions of the draft will not be supported, though some features
+are individually implemented as extensions described below.
 
 The protocol used by OpenSSH's ssh-agent is described in the file
 PROTOCOL.agent
@@ -31,7 +31,14 @@ The method is documented in:
 
 http://www.openssh.com/txt/draft-miller-secsh-compression-delayed-00.txt
 
-3. connection: Channel write close extension "eow@openssh.com"
+3. transport: New public key algorithms "ssh-rsa-cert-v00@openssh.com" and
+   "ssh-dsa-cert-v00@openssh.com"
+
+OpenSSH introduces two new public key algorithms to support certificate
+authentication for users and hostkeys. These methods are documented in
+the file PROTOCOL.certkeys
+
+4. connection: Channel write close extension "eow@openssh.com"
 
 The SSH connection protocol (rfc4254) provides the SSH_MSG_CHANNEL_EOF
 message to allow an endpoint to signal its peer that it will send no
@@ -70,7 +77,7 @@ message is only sent to OpenSSH peers (identified by banner).
 Other SSH implementations may be whitelisted to receive this message
 upon request.
 
-4. connection: disallow additional sessions extension
+5. connection: disallow additional sessions extension
    "no-more-sessions@openssh.com"
 
 Most SSH connections will only ever request a single session, but a
@@ -98,7 +105,7 @@ of this message, the no-more-sessions request is only sent to OpenSSH
 servers (identified by banner). Other SSH implementations may be
 whitelisted to receive this message upon request.
 
-5. connection: Tunnel forward extension "tun@openssh.com"
+6. connection: Tunnel forward extension "tun@openssh.com"
 
 OpenSSH supports layer 2 and layer 3 tunnelling via the "tun@openssh.com"
 channel type. This channel type supports forwarding of network packets
@@ -121,10 +128,10 @@ layer 2 frames or layer 3 packets. It may take one of the following values:
        SSH_TUNMODE_ETHERNET     2              /* layer 2 frames */
 
 The "tunnel unit number" specifies the remote interface number, or may
-be zero to allow the server to automatically chose an interface. A server
-that is not willing to open a client-specified unit should refuse the
-request with a SSH_MSG_CHANNEL_OPEN_FAILURE error. On successful open,
-the server should reply with SSH_MSG_CHANNEL_OPEN_SUCCESS.
+be 0x7fffffff to allow the server to automatically chose an interface. A
+server that is not willing to open a client-specified unit should refuse
+the request with a SSH_MSG_CHANNEL_OPEN_FAILURE error. On successful
+open, the server should reply with SSH_MSG_CHANNEL_OPEN_SUCCESS.
 
 Once established the client and server may exchange packet or frames
 over the tunnel channel by encapsulating them in SSH protocol strings
@@ -151,7 +158,7 @@ It may be one of:
 The "packet data" field consists of the IPv4/IPv6 datagram itself
 without any link layer header.
 
-The contents of the "data" field for layer 3 packets is:
+The contents of the "data" field for layer 2 packets is:
 
        uint32                  packet length
        byte[packet length]     frame
@@ -159,7 +166,7 @@ The contents of the "data" field for layer 3 packets is:
 The "frame" field contains an IEEE 802.3 Ethernet frame, including
 header.
 
-6. sftp: Reversal of arguments to SSH_FXP_SYMLINK
+7. sftp: Reversal of arguments to SSH_FXP_SYMLINK
 
 When OpenSSH's sftp-server was implemented, the order of the arguments
 to the SSH_FXP_SYMLINK method was inadvertently reversed. Unfortunately,
@@ -172,7 +179,7 @@ SSH_FXP_SYMLINK as follows:
        string          targetpath
        string          linkpath
 
-7. sftp: Server extension announcement in SSH_FXP_VERSION
+8. sftp: Server extension announcement in SSH_FXP_VERSION
 
 OpenSSH's sftp-server lists the extensions it supports using the
 standard extension announcement mechanism in the SSH_FXP_VERSION server
@@ -193,7 +200,7 @@ ever changed in an incompatible way. The server MAY advertise the same
 extension with multiple versions (though this is unlikely). Clients MUST
 check the version number before attempting to use the extension.
 
-8. sftp: Extension request "posix-rename@openssh.com"
+9. sftp: Extension request "posix-rename@openssh.com"
 
 This operation provides a rename operation with POSIX semantics, which
 are different to those provided by the standard SSH_FXP_RENAME in
@@ -210,7 +217,7 @@ rename(oldpath, newpath) and will respond with a SSH_FXP_STATUS message.
 This extension is advertised in the SSH_FXP_VERSION hello with version
 "1".
 
-9. sftp: Extension requests "statvfs@openssh.com" and
+10. sftp: Extension requests "statvfs@openssh.com" and
          "fstatvfs@openssh.com"
 
 These requests correspond to the statvfs and fstatvfs POSIX system
@@ -251,4 +258,4 @@ The values of the f_flag bitmask are as follows:
 Both the "statvfs@openssh.com" and "fstatvfs@openssh.com" extensions are
 advertised in the SSH_FXP_VERSION hello with version "2".
 
-$OpenBSD: PROTOCOL,v 1.12 2009/02/14 06:35:49 djm Exp $
+$OpenBSD: PROTOCOL,v 1.15 2010/02/26 20:29:54 djm Exp $
index 49adbdd..b34fcd3 100644 (file)
@@ -173,6 +173,15 @@ be added using the following request
        string                  key_comment
        constraint[]            key_constraints
 
+DSA certificates may be added with:
+       byte                    SSH2_AGENTC_ADD_IDENTITY or
+                               SSH2_AGENTC_ADD_ID_CONSTRAINED
+       string                  "ssh-dss-cert-v00@openssh.com"
+       string                  certificate
+       mpint                   dsa_private_key
+       string                  key_comment
+       constraint[]            key_constraints
+
 RSA keys may be added with this request:
 
        byte                    SSH2_AGENTC_ADD_IDENTITY or
@@ -187,6 +196,19 @@ RSA keys may be added with this request:
        string                  key_comment
        constraint[]            key_constraints
 
+RSA certificates may be added with this request:
+
+       byte                    SSH2_AGENTC_ADD_IDENTITY or
+                               SSH2_AGENTC_ADD_ID_CONSTRAINED
+       string                  "ssh-rsa-cert-v00@openssh.com"
+       string                  certificate
+       mpint                   rsa_d
+       mpint                   rsa_iqmp
+       mpint                   rsa_p
+       mpint                   rsa_q
+       string                  key_comment
+       constraint[]            key_constraints
+
 Note that the 'rsa_p' and 'rsa_q' parameters are sent in the reverse
 order to the protocol 1 add keys message. As with the corresponding
 protocol 1 "add key" request, the private key is overspecified to avoid
@@ -513,4 +535,4 @@ Locking and unlocking affects both protocol 1 and protocol 2 keys.
        SSH_AGENT_CONSTRAIN_LIFETIME                    1
        SSH_AGENT_CONSTRAIN_CONFIRM                     2
 
-$OpenBSD: PROTOCOL.agent,v 1.4 2008/07/01 23:12:47 stevesk Exp $
+$OpenBSD: PROTOCOL.agent,v 1.5 2010/02/26 20:29:54 djm Exp $
diff --git a/crypto/openssh/PROTOCOL.certkeys b/crypto/openssh/PROTOCOL.certkeys
new file mode 100644 (file)
index 0000000..1d1be13
--- /dev/null
@@ -0,0 +1,225 @@
+This document describes a simple public-key certificate authentication
+system for use by SSH.
+
+Background
+----------
+
+The SSH protocol currently supports a simple public key authentication
+mechanism. Unlike other public key implementations, SSH eschews the
+use of X.509 certificates and uses raw keys. This approach has some
+benefits relating to simplicity of configuration and minimisation
+of attack surface, but it does not support the important use-cases
+of centrally managed, passwordless authentication and centrally
+certified host keys.
+
+These protocol extensions build on the simple public key authentication
+system already in SSH to allow certificate-based authentication.
+The certificates used are not traditional X.509 certificates, with
+numerous options and complex encoding rules, but something rather
+more minimal: a key, some identity information and usage options
+that have been signed with some other trusted key.
+
+A sshd server may be configured to allow authentication via certified
+keys, by extending the existing ~/.ssh/authorized_keys mechanism
+to allow specification of certification authority keys in addition
+to raw user keys. The ssh client will support automatic verification
+of acceptance of certified host keys, by adding a similar ability
+to specify CA keys in ~/.ssh/known_hosts.
+
+Certified keys are represented using two new key types:
+ssh-rsa-cert-v01@openssh.com and ssh-dss-cert-v01@openssh.com that
+include certification information along with the public key that is used
+to sign challenges. ssh-keygen performs the CA signing operation.
+
+Protocol extensions
+-------------------
+
+The SSH wire protocol includes several extensibility mechanisms.
+These modifications shall take advantage of namespaced public key
+algorithm names to add support for certificate authentication without
+breaking the protocol - implementations that do not support the
+extensions will simply ignore them.
+
+Authentication using the new key formats described below proceeds
+using the existing SSH "publickey" authentication method described
+in RFC4252 section 7.
+
+New public key formats
+----------------------
+
+The ssh-rsa-cert-v01@openssh.com and ssh-dss-cert-v01@openssh.com key
+types take a similar high-level format (note: data types and
+encoding are as per RFC4251 section 5). The serialised wire encoding of
+these certificates is also used for storing them on disk.
+
+#define SSH_CERT_TYPE_USER    1
+#define SSH_CERT_TYPE_HOST    2
+
+RSA certificate
+
+    string    "ssh-rsa-cert-v01@openssh.com"
+    string    nonce
+    mpint     e
+    mpint     n
+    uint64    serial
+    uint32    type
+    string    key id
+    string    valid principals
+    uint64    valid after
+    uint64    valid before
+    string    critical options
+    string    extensions
+    string    reserved
+    string    signature key
+    string    signature
+
+DSA certificate
+
+    string    "ssh-dss-cert-v01@openssh.com"
+    string    nonce
+    mpint     p
+    mpint     q
+    mpint     g
+    mpint     y
+    uint64    serial
+    uint32    type
+    string    key id
+    string    valid principals
+    uint64    valid after
+    uint64    valid before
+    string    critical options
+    string    extensions
+    string    reserved
+    string    signature key
+    string    signature
+
+The nonce field is a CA-provided random bitstring of arbitrary length
+(but typically 16 or 32 bytes) included to make attacks that depend on
+inducing collisions in the signature hash infeasible.
+
+e and n are the RSA exponent and public modulus respectively.
+
+p, q, g, y are the DSA parameters as described in FIPS-186-2.
+
+serial is an optional certificate serial number set by the CA to
+provide an abbreviated way to refer to certificates from that CA.
+If a CA does not wish to number its certificates it must set this
+field to zero.
+
+type specifies whether this certificate is for identification of a user
+or a host using a SSH_CERT_TYPE_... value.
+
+key id is a free-form text field that is filled in by the CA at the time
+of signing; the intention is that the contents of this field are used to
+identify the identity principal in log messages.
+
+"valid principals" is a string containing zero or more principals as
+strings packed inside it. These principals list the names for which this
+certificate is valid; hostnames for SSH_CERT_TYPE_HOST certificates and
+usernames for SSH_CERT_TYPE_USER certificates. As a special case, a
+zero-length "valid principals" field means the certificate is valid for
+any principal of the specified type. XXX DNS wildcards?
+
+"valid after" and "valid before" specify a validity period for the
+certificate. Each represents a time in seconds since 1970-01-01
+00:00:00. A certificate is considered valid if:
+        valid after <= current time < valid before
+
+criticial options is a set of zero or more key options encoded as
+below. All such options are "critical" in the sense that an implementation
+must refuse to authorise a key that has an unrecognised option.
+
+extensions is a set of zero or more optional extensions. These extensions
+are not critical, and an implementation that encounters one that it does
+not recognise may safely ignore it.
+
+The reserved field is currently unused and is ignored in this version of
+the protocol.
+
+signature key contains the CA key used to sign the certificate.
+The valid key types for CA keys are ssh-rsa and ssh-dss. "Chained"
+certificates, where the signature key type is a certificate type itself
+are NOT supported. Note that it is possible for a RSA certificate key to
+be signed by a DSS CA key and vice-versa.
+
+signature is computed over all preceding fields from the initial string
+up to, and including the signature key. Signatures are computed and
+encoded according to the rules defined for the CA's public key algorithm
+(RFC4253 section 6.6 for ssh-rsa and ssh-dss).
+
+Critical options
+----------------
+
+The critical options section of the certificate specifies zero or more
+options on the certificates validity. The format of this field
+is a sequence of zero or more tuples:
+
+    string       name
+    string       data
+
+Options must be lexically ordered by "name" if they appear in the
+sequence.
+
+The name field identifies the option and the data field encodes
+option-specific information (see below). All options are
+"critical", if an implementation does not recognise a option
+then the validating party should refuse to accept the certificate.
+
+The supported options and the contents and structure of their
+data fields are:
+
+Name                    Format        Description
+-----------------------------------------------------------------------------
+force-command           string        Specifies a command that is executed
+                                      (replacing any the user specified on the
+                                      ssh command-line) whenever this key is
+                                      used for authentication.
+
+source-address          string        Comma-separated list of source addresses
+                                      from which this certificate is accepted
+                                      for authentication. Addresses are
+                                      specified in CIDR format (nn.nn.nn.nn/nn
+                                      or hhhh::hhhh/nn).
+                                      If this option is not present then
+                                      certificates may be presented from any
+                                      source address.
+
+Extensions
+----------
+
+The extensions section of the certificate specifies zero or more
+non-critical certificate extensions. The encoding and ordering of
+extensions in this field is identical to that of the critical options.
+If an implementation does not recognise an extension, then it should
+ignore it.
+
+The supported extensions and the contents and structure of their data
+fields are:
+
+Name                    Format        Description
+-----------------------------------------------------------------------------
+permit-X11-forwarding   empty         Flag indicating that X11 forwarding
+                                      should be permitted. X11 forwarding will
+                                      be refused if this option is absent.
+
+permit-agent-forwarding empty         Flag indicating that agent forwarding
+                                      should be allowed. Agent forwarding
+                                      must not be permitted unless this
+                                      option is present.
+
+permit-port-forwarding  empty         Flag indicating that port-forwarding
+                                      should be allowed. If this option is
+                                      not present then no port forwarding will
+                                      be allowed.
+
+permit-pty              empty         Flag indicating that PTY allocation
+                                      should be permitted. In the absence of
+                                      this option PTY allocation will be
+                                      disabled.
+
+permit-user-rc          empty         Flag indicating that execution of
+                                      ~/.ssh/rc should be permitted. Execution
+                                      of this script will not be permitted if
+                                      this option is not present.
+
+$OpenBSD: PROTOCOL.certkeys,v 1.7 2010/08/04 05:40:39 djm Exp $
diff --git a/crypto/openssh/PROTOCOL.mux b/crypto/openssh/PROTOCOL.mux
new file mode 100644 (file)
index 0000000..1d8c463
--- /dev/null
@@ -0,0 +1,203 @@
+This document describes the multiplexing protocol used by ssh(1)'s
+ControlMaster connection-sharing.
+
+Most messages from the client to the server contain a "request id" field.
+This field is returned in replies as "client request id" to facilitate
+matching of responses to requests.
+
+1. Connection setup
+
+When a multiplexing connection is made to a ssh(1) operating as a
+ControlMaster from a ssh(1) in multiplex slave mode, the first
+action of each is to exchange hello messages:
+
+       uint32  MUX_MSG_HELLO
+       uint32  protocol version
+       string  extension name [optional]
+       string  extension value [optional]
+       ...
+
+The current version of the mux protocol is 4. A slave should refuse
+to connect to a master that speaks an unsupported protocol version.
+Following the version identifier are zero or more extensions
+represented as a name/value pair. No extensions are currently
+defined.
+
+2. Opening sessions
+
+To open a new multiplexed session, a client may send the following
+request:
+
+       uint32  MUX_C_MSG_NEW_SESSION
+       uint32  request id
+       string  reserved
+       bool    want tty flag
+       bool    want X11 forwarding flag
+       bool    want agent flag
+       bool    subsystem flag
+       uint32  escape char
+       string  terminal type
+       string  command
+       string  environment string 0 [optional]
+       ...
+
+To disable the use of an escape character, "escape char" may be set
+to 0xffffffff. "terminal type" is generally set to the value of
+$TERM. zero or more environment strings may follow the command.
+
+The client then sends its standard input, output and error file
+descriptors (in that order) using Unix domain socket control messages.
+
+The contents of "reserved" are currently ignored.
+
+If successful, the server will reply with MUX_S_SESSION_OPENED
+
+       uint32  MUX_S_SESSION_OPENED
+       uint32  client request id
+       uint32  session id
+
+Otherwise it will reply with an error: MUX_S_PERMISSION_DENIED or
+MUX_S_FAILURE.
+
+Once the server has received the fds, it will respond with MUX_S_OK
+indicating that the session is up. The client now waits for the
+session to end. When it does, the server will send an exit status
+message:
+
+       uint32  MUX_S_EXIT_MESSAGE
+       uint32  session id
+       uint32  exit value
+
+The client should exit with this value to mimic the behaviour of a
+non-multiplexed ssh(1) connection. Two additional cases that the
+client must cope with are it receiving a signal itself and the
+server disconnecting without sending an exit message.
+
+3. Health checks
+
+The client may request a health check/PID report from a server:
+
+       uint32  MUX_C_ALIVE_CHECK
+       uint32  request id
+
+The server replies with:
+
+       uint32  MUX_S_ALIVE
+       uint32  client request id
+       uint32  server pid
+
+4. Remotely terminating a master
+
+A client may request that a master terminate immediately:
+
+       uint32  MUX_C_TERMINATE
+       uint32  request id
+
+The server will reply with one of MUX_S_OK or MUX_S_PERMISSION_DENIED.
+
+5. Requesting establishment of port forwards
+
+A client may request the master to establish a port forward:
+
+       uint32  MUX_C_OPEN_FORWARD
+       uint32  request id
+       uint32  forwarding type
+       string  listen host
+       string  listen port
+       string  connect host
+       string  connect port
+
+forwarding type may be MUX_FWD_LOCAL, MUX_FWD_REMOTE, MUX_FWD_DYNAMIC.
+
+A server may reply with a MUX_S_OK, a MUX_S_REMOTE_PORT, a
+MUX_S_PERMISSION_DENIED or a MUX_S_FAILURE.
+
+For dynamically allocated listen port the server replies with
+
+       uint32  MUX_S_REMOTE_PORT
+       uint32  client request id
+       uint32  allocated remote listen port
+
+5. Requesting closure of port forwards
+
+A client may request the master to establish a port forward:
+
+       uint32  MUX_C_OPEN_FORWARD
+       uint32  request id
+       uint32  forwarding type
+       string  listen host
+       string  listen port
+       string  connect host
+       string  connect port
+
+forwarding type may be MUX_FWD_LOCAL, MUX_FWD_REMOTE, MUX_FWD_DYNAMIC.
+
+A server may reply with a MUX_S_OK, a MUX_S_PERMISSION_DENIED or a
+MUX_S_FAILURE.
+
+6. Requesting stdio forwarding
+
+A client may request the master to establish a stdio forwarding:
+
+       uint32  MUX_C_NEW_STDIO_FWD
+       uint32  request id
+       string  reserved
+       string  connect host
+       string  connect port
+
+The client then sends its standard input and output file descriptors
+(in that order) using Unix domain socket control messages.
+
+The contents of "reserved" are currently ignored.
+
+A server may reply with a MUX_S_SESSION_OPEED, a MUX_S_PERMISSION_DENIED
+or a MUX_S_FAILURE.
+
+7. Status messages
+
+The MUX_S_OK message is empty:
+
+       uint32  MUX_S_OK
+       uint32  client request id
+
+The MUX_S_PERMISSION_DENIED and MUX_S_FAILURE include a reason:
+
+       uint32  MUX_S_PERMISSION_DENIED
+       uint32  client request id
+       string  reason
+
+       uint32  MUX_S_FAILURE
+       uint32  client request id
+       string  reason
+
+7. Protocol numbers
+
+#define MUX_MSG_HELLO          0x00000001
+#define MUX_C_NEW_SESSION      0x10000002
+#define MUX_C_ALIVE_CHECK      0x10000004
+#define MUX_C_TERMINATE                0x10000005
+#define MUX_C_OPEN_FORWARD     0x10000006
+#define MUX_C_CLOSE_FORWARD    0x10000007
+#define MUX_S_OK               0x80000001
+#define MUX_S_PERMISSION_DENIED        0x80000002
+#define MUX_S_FAILURE          0x80000003
+#define MUX_S_EXIT_MESSAGE     0x80000004
+#define MUX_S_ALIVE            0x80000005
+#define MUX_S_SESSION_OPENED   0x80000006
+#define MUX_S_REMOTE_PORT      0x80000007
+
+#define MUX_FWD_LOCAL  1
+#define MUX_FWD_REMOTE 2
+#define MUX_FWD_DYNAMIC        3
+
+XXX TODO
+XXX extended status (e.g. report open channels / forwards)
+XXX graceful close (delete listening socket, but keep existing sessions active)
+XXX lock (maybe)
+XXX watch in/out traffic (pre/post crypto)
+XXX inject packet (what about replies)
+XXX server->client error/warning notifications
+XXX port0 rfwd (need custom response message)
+XXX send signals via mux
+
+$OpenBSD: PROTOCOL.mux,v 1.2 2010/05/16 12:55:51 markus Exp $
index 8538e8c..4eaa545 100644 (file)
@@ -1,4 +1,4 @@
-See http://www.openssh.com/txt/release-5.3 for the release notes.
+See http://www.openssh.com/txt/release-5.6 for the release notes.
 
 - A Japanese translation of this document and of the OpenSSH FAQ is
 - available at http://www.unixuser.org/~haruyama/security/openssh/index.html
@@ -62,4 +62,4 @@ References -
 [6] http://www.openbsd.org/cgi-bin/man.cgi?query=style&sektion=9
 [7] http://www.openssh.com/faq.html
 
-$Id: README,v 1.70.4.1 2009/09/26 04:11:47 djm Exp $
+$Id: README,v 1.74 2010/08/08 16:32:06 djm Exp $
index 82b0a56..8b7cee9 100644 (file)
@@ -4,7 +4,6 @@ Makefile.in
 OVERVIEW
 README.platform
 README.privsep
-README.smartcard
 TODO
 WARNING.RNG
 aclocal.m4
@@ -57,6 +56,7 @@ openbsd-compat/port-irix.c
 openbsd-compat/port-linux.c
 openbsd-compat/port-solaris.c
 openbsd-compat/port-uw.c
+openbsd-compat/pwcache.c
 openbsd-compat/readpassphrase.c
 openbsd-compat/realpath.c
 openbsd-compat/regress/
@@ -68,6 +68,7 @@ openbsd-compat/sigact.c
 openbsd-compat/strlcat.c
 openbsd-compat/strlcpy.c
 openbsd-compat/strmode.c
+openbsd-compat/strptime.c
 openbsd-compat/strsep.c
 openbsd-compat/strtoll.c
 openbsd-compat/strtonum.c
@@ -79,7 +80,6 @@ openssh.xml.in
 opensshd.init.in
 regress/
 scard/
-scard.h
 scp.0
 sftp-server.0
 sftp.0
@@ -88,6 +88,7 @@ ssh-agent.0
 ssh-keygen.0
 ssh-keyscan.0
 ssh-keysign.0
+ssh-pkcs11-helper.0
 ssh-rand-helper.0
 ssh-rand-helper.8
 ssh-rand-helper.c
index d39885b..5b6773c 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: addrmatch.c,v 1.4 2008/12/10 03:55:20 stevesk Exp $ */
+/*     $OpenBSD: addrmatch.c,v 1.5 2010/02/26 20:29:54 djm Exp $ */
 
 /*
  * Copyright (c) 2004-2008 Damien Miller <djm@mindrot.org>
@@ -126,6 +126,8 @@ addr_netmask(int af, u_int l, struct xaddr *n)
        switch (af) {
        case AF_INET:
                n->af = AF_INET;
+               if (l == 0)
+                       return 0;
                n->v4.s_addr = htonl((0xffffffff << (32 - l)) & 0xffffffff);
                return 0;
        case AF_INET6:
@@ -422,3 +424,77 @@ addr_match_list(const char *addr, const char *_list)
 
        return ret;
 }
+
+/*
+ * Match "addr" against list CIDR list "_list". Lexical wildcards and
+ * negation are not supported. If "addr" == NULL, will verify structure
+ * of "_list".
+ *
+ * Returns 1 on match found (never returned when addr == NULL).
+ * Returns 0 on if no match found, or no errors found when addr == NULL.
+ * Returns -1 on error
+ */
+int
+addr_match_cidr_list(const char *addr, const char *_list)
+{
+       char *list, *cp, *o;
+       struct xaddr try_addr, match_addr;
+       u_int masklen;
+       int ret = 0, r;
+
+       if (addr != NULL && addr_pton(addr, &try_addr) != 0) {
+               debug2("%s: couldn't parse address %.100s", __func__, addr);
+               return 0;
+       }
+       if ((o = list = strdup(_list)) == NULL)
+               return -1;
+       while ((cp = strsep(&list, ",")) != NULL) {
+               if (*cp == '\0') {
+                       error("%s: empty entry in list \"%.100s\"",
+                           __func__, o);
+                       ret = -1;
+                       break;
+               }
+
+               /*
+                * NB. This function is called in pre-auth with untrusted data,
+                * so be extra paranoid about junk reaching getaddrino (via
+                * addr_pton_cidr).
+                */
+
+               /* Stop junk from reaching getaddrinfo. +3 is for masklen */
+               if (strlen(cp) > INET6_ADDRSTRLEN + 3) {
+                       error("%s: list entry \"%.100s\" too long",
+                           __func__, cp);
+                       ret = -1;
+                       break;
+               }
+#define VALID_CIDR_CHARS "0123456789abcdefABCDEF.:/"
+               if (strspn(cp, VALID_CIDR_CHARS) != strlen(cp)) {
+                       error("%s: list entry \"%.100s\" contains invalid "
+                           "characters", __func__, cp);
+                       ret = -1;
+               }
+
+               /* Prefer CIDR address matching */
+               r = addr_pton_cidr(cp, &match_addr, &masklen);
+               if (r == -1) {
+                       error("Invalid network entry \"%.100s\"", cp);
+                       ret = -1;
+                       break;
+               } else if (r == -2) {
+                       error("Inconsistent mask length for "
+                           "network \"%.100s\"", cp);
+                       ret = -1;
+                       break;
+               } else if (r == 0 && addr != NULL) {
+                       if (addr_netmatch(&try_addr, &match_addr,
+                           masklen) == 0)
+                               ret = 1;
+                       continue;
+               }
+       }
+       xfree(o);
+
+       return ret;
+}
index 8682881..d019fe2 100644 (file)
@@ -78,6 +78,11 @@ auth_krb5_password(Authctxt *authctxt, const char *password)
        krb5_error_code problem;
        krb5_ccache ccache = NULL;
        int len;
+       char *client, *platform_client;
+
+       /* get platform-specific kerberos client principal name (if it exists) */
+       platform_client = platform_krb5_get_principal_name(authctxt->pw->pw_name);
+       client = platform_client ? platform_client : authctxt->pw->pw_name;
 
        temporarily_use_uid(authctxt->pw);
 
@@ -85,7 +90,7 @@ auth_krb5_password(Authctxt *authctxt, const char *password)
        if (problem)
                goto out;
 
-       problem = krb5_parse_name(authctxt->krb5_ctx, authctxt->pw->pw_name,
+       problem = krb5_parse_name(authctxt->krb5_ctx, client,
                    &authctxt->krb5_user);
        if (problem)
                goto out;
@@ -141,8 +146,7 @@ auth_krb5_password(Authctxt *authctxt, const char *password)
        if (problem)
                goto out;
 
-       if (!krb5_kuserok(authctxt->krb5_ctx, authctxt->krb5_user,
-                         authctxt->pw->pw_name)) {
+       if (!krb5_kuserok(authctxt->krb5_ctx, authctxt->krb5_user, client)) {
                problem = -1;
                goto out;
        }
@@ -176,6 +180,9 @@ auth_krb5_password(Authctxt *authctxt, const char *password)
 
  out:
        restore_uid();
+       
+       if (platform_client != NULL)
+               xfree(platform_client);
 
        if (problem) {
                if (ccache)
index ab085c2..a704024 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: auth-options.c,v 1.44 2009/01/22 10:09:16 djm Exp $ */
+/* $OpenBSD: auth-options.c,v 1.52 2010/05/20 23:46:02 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
 #include "canohost.h"
 #include "buffer.h"
 #include "channels.h"
-#include "auth-options.h"
 #include "servconf.h"
 #include "misc.h"
 #include "key.h"
+#include "auth-options.h"
 #include "hostfile.h"
 #include "auth.h"
 #ifdef GSSAPI
@@ -44,6 +44,7 @@ int no_agent_forwarding_flag = 0;
 int no_x11_forwarding_flag = 0;
 int no_pty_flag = 0;
 int no_user_rc = 0;
+int key_is_cert_authority = 0;
 
 /* "command=" option. */
 char *forced_command = NULL;
@@ -54,6 +55,9 @@ struct envstring *custom_environment = NULL;
 /* "tunnel=" option. */
 int forced_tun_device = -1;
 
+/* "principals=" option. */
+char *authorized_principals = NULL;
+
 extern ServerOptions options;
 
 void
@@ -64,6 +68,7 @@ auth_clear_options(void)
        no_pty_flag = 0;
        no_x11_forwarding_flag = 0;
        no_user_rc = 0;
+       key_is_cert_authority = 0;
        while (custom_environment) {
                struct envstring *ce = custom_environment;
                custom_environment = ce->next;
@@ -74,9 +79,12 @@ auth_clear_options(void)
                xfree(forced_command);
                forced_command = NULL;
        }
+       if (authorized_principals) {
+               xfree(authorized_principals);
+               authorized_principals = NULL;
+       }
        forced_tun_device = -1;
        channel_clear_permitted_opens();
-       auth_debug_reset();
 }
 
 /*
@@ -96,6 +104,12 @@ auth_parse_options(struct passwd *pw, char *opts, char *file, u_long linenum)
                return 1;
 
        while (*opts && *opts != ' ' && *opts != '\t') {
+               cp = "cert-authority";
+               if (strncasecmp(opts, cp, strlen(cp)) == 0) {
+                       key_is_cert_authority = 1;
+                       opts += strlen(cp);
+                       goto next_option;
+               }
                cp = "no-port-forwarding";
                if (strncasecmp(opts, cp, strlen(cp)) == 0) {
                        auth_debug_add("Port forwarding disabled.");
@@ -134,6 +148,8 @@ auth_parse_options(struct passwd *pw, char *opts, char *file, u_long linenum)
                cp = "command=\"";
                if (strncasecmp(opts, cp, strlen(cp)) == 0) {
                        opts += strlen(cp);
+                       if (forced_command != NULL)
+                               xfree(forced_command);
                        forced_command = xmalloc(strlen(opts) + 1);
                        i = 0;
                        while (*opts) {
@@ -160,6 +176,38 @@ auth_parse_options(struct passwd *pw, char *opts, char *file, u_long linenum)
                        opts++;
                        goto next_option;
                }
+               cp = "principals=\"";
+               if (strncasecmp(opts, cp, strlen(cp)) == 0) {
+                       opts += strlen(cp);
+                       if (authorized_principals != NULL)
+                               xfree(authorized_principals);
+                       authorized_principals = xmalloc(strlen(opts) + 1);
+                       i = 0;
+                       while (*opts) {
+                               if (*opts == '"')
+                                       break;
+                               if (*opts == '\\' && opts[1] == '"') {
+                                       opts += 2;
+                                       authorized_principals[i++] = '"';
+                                       continue;
+                               }
+                               authorized_principals[i++] = *opts++;
+                       }
+                       if (!*opts) {
+                               debug("%.100s, line %lu: missing end quote",
+                                   file, linenum);
+                               auth_debug_add("%.100s, line %lu: missing end quote",
+                                   file, linenum);
+                               xfree(authorized_principals);
+                               authorized_principals = NULL;
+                               goto bad_option;
+                       }
+                       authorized_principals[i] = '\0';
+                       auth_debug_add("principals: %.900s",
+                           authorized_principals);
+                       opts++;
+                       goto next_option;
+               }
                cp = "environment=\"";
                if (options.permit_user_env &&
                    strncasecmp(opts, cp, strlen(cp)) == 0) {
@@ -356,9 +404,6 @@ next_option:
                /* Process the next option. */
        }
 
-       if (!use_privsep)
-               auth_debug_send();
-
        /* grant access */
        return 1;
 
@@ -368,9 +413,237 @@ bad_option:
        auth_debug_add("Bad options in %.100s file, line %lu: %.50s",
            file, linenum, opts);
 
-       if (!use_privsep)
-               auth_debug_send();
-
        /* deny access */
        return 0;
 }
+
+#define OPTIONS_CRITICAL       1
+#define OPTIONS_EXTENSIONS     2
+static int
+parse_option_list(u_char *optblob, size_t optblob_len, struct passwd *pw,
+    u_int which, int crit,
+    int *cert_no_port_forwarding_flag,
+    int *cert_no_agent_forwarding_flag,
+    int *cert_no_x11_forwarding_flag,
+    int *cert_no_pty_flag,
+    int *cert_no_user_rc,
+    char **cert_forced_command,
+    int *cert_source_address_done)
+{
+       char *command, *allowed;
+       const char *remote_ip;
+       u_char *name = NULL, *data_blob = NULL;
+       u_int nlen, dlen, clen;
+       Buffer c, data;
+       int ret = -1, found;
+
+       buffer_init(&data);
+
+       /* Make copy to avoid altering original */
+       buffer_init(&c);
+       buffer_append(&c, optblob, optblob_len);
+
+       while (buffer_len(&c) > 0) {
+               if ((name = buffer_get_string_ret(&c, &nlen)) == NULL ||
+                   (data_blob = buffer_get_string_ret(&c, &dlen)) == NULL) {
+                       error("Certificate options corrupt");
+                       goto out;
+               }
+               buffer_append(&data, data_blob, dlen);
+               debug3("found certificate option \"%.100s\" len %u",
+                   name, dlen);
+               if (strlen(name) != nlen) {
+                       error("Certificate constraint name contains \\0");
+                       goto out;
+               }
+               found = 0;
+               if ((which & OPTIONS_EXTENSIONS) != 0) {
+                       if (strcmp(name, "permit-X11-forwarding") == 0) {
+                               *cert_no_x11_forwarding_flag = 0;
+                               found = 1;
+                       } else if (strcmp(name,
+                           "permit-agent-forwarding") == 0) {
+                               *cert_no_agent_forwarding_flag = 0;
+                               found = 1;
+                       } else if (strcmp(name,
+                           "permit-port-forwarding") == 0) {
+                               *cert_no_port_forwarding_flag = 0;
+                               found = 1;
+                       } else if (strcmp(name, "permit-pty") == 0) {
+                               *cert_no_pty_flag = 0;
+                               found = 1;
+                       } else if (strcmp(name, "permit-user-rc") == 0) {
+                               *cert_no_user_rc = 0;
+                               found = 1;
+                       }
+               }
+               if (!found && (which & OPTIONS_CRITICAL) != 0) {
+                       if (strcmp(name, "force-command") == 0) {
+                               if ((command = buffer_get_string_ret(&data,
+                                   &clen)) == NULL) {
+                                       error("Certificate constraint \"%s\" "
+                                           "corrupt", name);
+                                       goto out;
+                               }
+                               if (strlen(command) != clen) {
+                                       error("force-command constraint "
+                                           "contains \\0");
+                                       goto out;
+                               }
+                               if (*cert_forced_command != NULL) {
+                                       error("Certificate has multiple "
+                                           "force-command options");
+                                       xfree(command);
+                                       goto out;
+                               }
+                               *cert_forced_command = command;
+                               found = 1;
+                       }
+                       if (strcmp(name, "source-address") == 0) {
+                               if ((allowed = buffer_get_string_ret(&data,
+                                   &clen)) == NULL) {
+                                       error("Certificate constraint "
+                                           "\"%s\" corrupt", name);
+                                       goto out;
+                               }
+                               if (strlen(allowed) != clen) {
+                                       error("source-address constraint "
+                                           "contains \\0");
+                                       goto out;
+                               }
+                               if ((*cert_source_address_done)++) {
+                                       error("Certificate has multiple "
+                                           "source-address options");
+                                       xfree(allowed);
+                                       goto out;
+                               }
+                               remote_ip = get_remote_ipaddr();
+                               switch (addr_match_cidr_list(remote_ip,
+                                   allowed)) {
+                               case 1:
+                                       /* accepted */
+                                       xfree(allowed);
+                                       break;
+                               case 0:
+                                       /* no match */
+                                       logit("Authentication tried for %.100s "
+                                           "with valid certificate but not "
+                                           "from a permitted host "
+                                           "(ip=%.200s).", pw->pw_name,
+                                           remote_ip);
+                                       auth_debug_add("Your address '%.200s' "
+                                           "is not permitted to use this "
+                                           "certificate for login.",
+                                           remote_ip);
+                                       xfree(allowed);
+                                       goto out;
+                               case -1:
+                                       error("Certificate source-address "
+                                           "contents invalid");
+                                       xfree(allowed);
+                                       goto out;
+                               }
+                               found = 1;
+                       }
+               }
+
+               if (!found) {
+                       if (crit) {
+                               error("Certificate critical option \"%s\" "
+                                   "is not supported", name);
+                               goto out;
+                       } else {
+                               logit("Certificate extension \"%s\" "
+                                   "is not supported", name);
+                       }
+               } else if (buffer_len(&data) != 0) {
+                       error("Certificate option \"%s\" corrupt "
+                           "(extra data)", name);
+                       goto out;
+               }
+               buffer_clear(&data);
+               xfree(name);
+               xfree(data_blob);
+               name = data_blob = NULL;
+       }
+       /* successfully parsed all options */
+       ret = 0;
+
+ out:
+       if (ret != 0 &&
+           cert_forced_command != NULL &&
+           *cert_forced_command != NULL) {
+               xfree(*cert_forced_command);
+               *cert_forced_command = NULL;
+       }
+       if (name != NULL)
+               xfree(name);
+       if (data_blob != NULL)
+               xfree(data_blob);
+       buffer_free(&data);
+       buffer_free(&c);
+       return ret;
+}
+
+/*
+ * Set options from critical certificate options. These supersede user key
+ * options so this must be called after auth_parse_options().
+ */
+int
+auth_cert_options(Key *k, struct passwd *pw)
+{
+       int cert_no_port_forwarding_flag = 1;
+       int cert_no_agent_forwarding_flag = 1;
+       int cert_no_x11_forwarding_flag = 1;
+       int cert_no_pty_flag = 1;
+       int cert_no_user_rc = 1;
+       char *cert_forced_command = NULL;
+       int cert_source_address_done = 0;
+
+       if (key_cert_is_legacy(k)) {
+               /* All options are in the one field for v00 certs */
+               if (parse_option_list(buffer_ptr(&k->cert->critical),
+                   buffer_len(&k->cert->critical), pw,
+                   OPTIONS_CRITICAL|OPTIONS_EXTENSIONS, 1,
+                   &cert_no_port_forwarding_flag,
+                   &cert_no_agent_forwarding_flag,
+                   &cert_no_x11_forwarding_flag,
+                   &cert_no_pty_flag,
+                   &cert_no_user_rc,
+                   &cert_forced_command,
+                   &cert_source_address_done) == -1)
+                       return -1;
+       } else {
+               /* Separate options and extensions for v01 certs */
+               if (parse_option_list(buffer_ptr(&k->cert->critical),
+                   buffer_len(&k->cert->critical), pw,
+                   OPTIONS_CRITICAL, 1, NULL, NULL, NULL, NULL, NULL,
+                   &cert_forced_command,
+                   &cert_source_address_done) == -1)
+                       return -1;
+               if (parse_option_list(buffer_ptr(&k->cert->extensions),
+                   buffer_len(&k->cert->extensions), pw,
+                   OPTIONS_EXTENSIONS, 1,
+                   &cert_no_port_forwarding_flag,
+                   &cert_no_agent_forwarding_flag,
+                   &cert_no_x11_forwarding_flag,
+                   &cert_no_pty_flag,
+                   &cert_no_user_rc,
+                   NULL, NULL) == -1)
+                       return -1;
+       }
+
+       no_port_forwarding_flag |= cert_no_port_forwarding_flag;
+       no_agent_forwarding_flag |= cert_no_agent_forwarding_flag;
+       no_x11_forwarding_flag |= cert_no_x11_forwarding_flag;
+       no_pty_flag |= cert_no_pty_flag;
+       no_user_rc |= cert_no_user_rc;
+       /* CA-specified forced command supersedes key option */
+       if (cert_forced_command != NULL) {
+               if (forced_command != NULL)
+                       xfree(forced_command);
+               forced_command = cert_forced_command;
+       }
+       return 0;
+}
+
index 14488f7..7455c94 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: auth-options.h,v 1.17 2008/03/26 21:28:14 djm Exp $ */
+/* $OpenBSD: auth-options.h,v 1.20 2010/05/07 11:30:29 djm Exp $ */
 
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -30,8 +30,11 @@ extern int no_user_rc;
 extern char *forced_command;
 extern struct envstring *custom_environment;
 extern int forced_tun_device;
+extern int key_is_cert_authority;
+extern char *authorized_principals;
 
 int    auth_parse_options(struct passwd *, char *, char *, u_long);
 void   auth_clear_options(void);
+int    auth_cert_options(Key *, struct passwd *);
 
 #endif
index eca7502..b21a0f4 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: auth-rh-rsa.c,v 1.42 2006/08/03 03:34:41 deraadt Exp $ */
+/* $OpenBSD: auth-rh-rsa.c,v 1.43 2010/03/04 10:36:03 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -44,6 +44,9 @@ auth_rhosts_rsa_key_allowed(struct passwd *pw, char *cuser, char *chost,
 {
        HostStatus host_status;
 
+       if (auth_key_is_revoked(client_host_key))
+               return 0;
+
        /* Check if we would accept it using rhosts authentication. */
        if (!auth_rhosts(pw, cuser))
                return 0;
index 5c12967..06ae7f0 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: auth-rhosts.c,v 1.43 2008/06/13 14:18:51 dtucker Exp $ */
+/* $OpenBSD: auth-rhosts.c,v 1.44 2010/03/07 11:57:13 dtucker Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -317,11 +317,5 @@ int
 auth_rhosts2(struct passwd *pw, const char *client_user, const char *hostname,
     const char *ipaddr)
 {
-       int ret;
-
-       auth_debug_reset();
-       ret = auth_rhosts2_raw(pw, client_user, hostname, ipaddr);
-       if (!use_privsep)
-               auth_debug_send();
-       return ret;
+       return auth_rhosts2_raw(pw, client_user, hostname, ipaddr);
 }
index bf54620..56702d1 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: auth-rsa.c,v 1.73 2008/07/02 12:03:51 dtucker Exp $ */
+/* $OpenBSD: auth-rsa.c,v 1.78 2010/07/13 23:13:16 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
 #include "uidswap.h"
 #include "match.h"
 #include "buffer.h"
-#include "auth-options.h"
 #include "pathnames.h"
 #include "log.h"
 #include "servconf.h"
 #include "key.h"
+#include "auth-options.h"
 #include "hostfile.h"
 #include "auth.h"
 #ifdef GSSAPI
@@ -94,6 +94,9 @@ auth_rsa_verify_response(Key *key, BIGNUM *challenge, u_char response[16])
        MD5_CTX md;
        int len;
 
+       if (auth_key_is_revoked(key))
+               return 0;
+
        /* don't allow short keys */
        if (BN_num_bits(key->rsa->n) < SSH_RSA_MINIMUM_MODULUS_SIZE) {
                error("auth_rsa_verify_response: RSA modulus too small: %d < minimum %d bits",
@@ -113,7 +116,7 @@ auth_rsa_verify_response(Key *key, BIGNUM *challenge, u_char response[16])
        MD5_Final(mdbuf, &md);
 
        /* Verify that the response is the original challenge. */
-       if (memcmp(response, mdbuf, 16) != 0) {
+       if (timingsafe_bcmp(response, mdbuf, 16) != 0) {
                /* Wrong answer. */
                return (0);
        }
@@ -253,7 +256,8 @@ auth_rsa_key_allowed(struct passwd *pw, BIGNUM *client_n, Key **rkey)
                 */
                if (!auth_parse_options(pw, key_options, file, linenum))
                        continue;
-
+               if (key_is_cert_authority)
+                       continue;
                /* break out, this key is allowed */
                allowed = 1;
                break;
index 3585daa..dba1e65 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: auth.c,v 1.80 2008/11/04 07:58:09 djm Exp $ */
+/* $OpenBSD: auth.c,v 1.89 2010/08/04 05:42:47 djm Exp $ */
 /*
  * Copyright (c) 2000 Markus Friedl.  All rights reserved.
  *
@@ -69,6 +69,7 @@
 #ifdef GSSAPI
 #include "ssh-gss.h"
 #endif
+#include "authfile.h"
 #include "monitor_wrap.h"
 
 /* import */
@@ -95,7 +96,6 @@ allowed_user(struct passwd * pw)
 {
        struct stat st;
        const char *hostname = NULL, *ipaddr = NULL, *passwd = NULL;
-       char *shell;
        u_int i;
 #ifdef USE_SHADOW
        struct spwd *spw = NULL;
@@ -143,7 +143,7 @@ allowed_user(struct passwd * pw)
                        locked = 1;
 #endif
 #ifdef USE_LIBIAF
-               free(passwd);
+               free((void *) passwd);
 #endif /* USE_LIBIAF */
                if (locked) {
                        logit("User %.100s not allowed because account is locked",
@@ -153,22 +153,28 @@ allowed_user(struct passwd * pw)
        }
 
        /*
-        * Get the shell from the password data.  An empty shell field is
-        * legal, and means /bin/sh.
+        * Deny if shell does not exist or is not executable unless we
+        * are chrooting.
         */
-       shell = (pw->pw_shell[0] == '\0') ? _PATH_BSHELL : pw->pw_shell;
-
-       /* deny if shell does not exists or is not executable */
-       if (stat(shell, &st) != 0) {
-               logit("User %.100s not allowed because shell %.100s does not exist",
-                   pw->pw_name, shell);
-               return 0;
-       }
-       if (S_ISREG(st.st_mode) == 0 ||
-           (st.st_mode & (S_IXOTH|S_IXUSR|S_IXGRP)) == 0) {
-               logit("User %.100s not allowed because shell %.100s is not executable",
-                   pw->pw_name, shell);
-               return 0;
+       if (options.chroot_directory == NULL ||
+           strcasecmp(options.chroot_directory, "none") == 0) {
+               char *shell = xstrdup((pw->pw_shell[0] == '\0') ?
+                   _PATH_BSHELL : pw->pw_shell); /* empty = /bin/sh */
+
+               if (stat(shell, &st) != 0) {
+                       logit("User %.100s not allowed because shell %.100s "
+                           "does not exist", pw->pw_name, shell);
+                       xfree(shell);
+                       return 0;
+               }
+               if (S_ISREG(st.st_mode) == 0 ||
+                   (st.st_mode & (S_IXOTH|S_IXUSR|S_IXGRP)) == 0) {
+                       logit("User %.100s not allowed because shell %.100s "
+                           "is not executable", pw->pw_name, shell);
+                       xfree(shell);
+                       return 0;
+               }
+               xfree(shell);
        }
 
        if (options.num_deny_users > 0 || options.num_allow_users > 0 ||
@@ -360,6 +366,14 @@ authorized_keys_file2(struct passwd *pw)
        return expand_authorized_keys(options.authorized_keys_file2, pw);
 }
 
+char *
+authorized_principals_file(struct passwd *pw)
+{
+       if (options.authorized_principals_file == NULL)
+               return NULL;
+       return expand_authorized_keys(options.authorized_principals_file, pw);
+}
+
 /* return ok if key exists in sysfile or userfile */
 HostStatus
 check_key_in_hostfiles(struct passwd *pw, Key *key, const char *host,
@@ -371,7 +385,7 @@ check_key_in_hostfiles(struct passwd *pw, Key *key, const char *host,
        HostStatus host_status;
 
        /* Check if we know the host and its host key. */
-       found = key_new(key->type);
+       found = key_new(key_is_cert(key) ? KEY_UNSPEC : key->type);
        host_status = check_host_in_hostfile(sysfile, host, key, found, NULL);
 
        if (host_status != HOST_OK && userfile != NULL) {
@@ -383,6 +397,8 @@ check_key_in_hostfiles(struct passwd *pw, Key *key, const char *host,
                        logit("Authentication refused for %.100s: "
                            "bad owner or modes for %.200s",
                            pw->pw_name, user_hostfile);
+                       auth_debug_add("Ignored %.200s: bad ownership or modes",
+                           user_hostfile);
                } else {
                        temporarily_use_uid(pw);
                        host_status = check_host_in_hostfile(user_hostfile,
@@ -455,7 +471,7 @@ secure_filename(FILE *f, const char *file, struct passwd *pw,
                        return -1;
                }
 
-               /* If are passed the homedir then we can stop */
+               /* If are past the homedir then we can stop */
                if (comparehome && strcmp(homedir, buf) == 0) {
                        debug3("secure_filename: terminating check at '%s'",
                            buf);
@@ -471,28 +487,29 @@ secure_filename(FILE *f, const char *file, struct passwd *pw,
        return 0;
 }
 
-FILE *
-auth_openkeyfile(const char *file, struct passwd *pw, int strict_modes)
+static FILE *
+auth_openfile(const char *file, struct passwd *pw, int strict_modes,
+    int log_missing, char *file_type)
 {
        char line[1024];
        struct stat st;
        int fd;
        FILE *f;
 
-       /*
-        * Open the file containing the authorized keys
-        * Fail quietly if file does not exist
-        */
-       if ((fd = open(file, O_RDONLY|O_NONBLOCK)) == -1)
+       if ((fd = open(file, O_RDONLY|O_NONBLOCK)) == -1) {
+               if (log_missing || errno != ENOENT)
+                       debug("Could not open %s '%s': %s", file_type, file,
+                          strerror(errno));
                return NULL;
+       }
 
        if (fstat(fd, &st) < 0) {
                close(fd);
                return NULL;
        }
        if (!S_ISREG(st.st_mode)) {
-               logit("User %s authorized keys %s is not a regular file",
-                   pw->pw_name, file);
+               logit("User %s %s %s is not a regular file",
+                   pw->pw_name, file_type, file);
                close(fd);
                return NULL;
        }
@@ -505,12 +522,27 @@ auth_openkeyfile(const char *file, struct passwd *pw, int strict_modes)
            secure_filename(f, file, pw, line, sizeof(line)) != 0) {
                fclose(f);
                logit("Authentication refused: %s", line);
+               auth_debug_add("Ignored %s: %s", file_type, line);
                return NULL;
        }
 
        return f;
 }
 
+
+FILE *
+auth_openkeyfile(const char *file, struct passwd *pw, int strict_modes)
+{
+       return auth_openfile(file, pw, strict_modes, 1, "authorized keys");
+}
+
+FILE *
+auth_openprincipals(const char *file, struct passwd *pw, int strict_modes)
+{
+       return auth_openfile(file, pw, strict_modes, 0,
+           "authorized principals");
+}
+
 struct passwd *
 getpwnamallow(const char *user)
 {
@@ -525,7 +557,28 @@ getpwnamallow(const char *user)
        parse_server_match_config(&options, user,
            get_canonical_hostname(options.use_dns), get_remote_ipaddr());
 
+#if defined(_AIX) && defined(HAVE_SETAUTHDB)
+       aix_setauthdb(user);
+#endif
+
        pw = getpwnam(user);
+
+#if defined(_AIX) && defined(HAVE_SETAUTHDB)
+       aix_restoreauthdb();
+#endif
+#ifdef HAVE_CYGWIN
+       /*
+        * Windows usernames are case-insensitive.  To avoid later problems
+        * when trying to match the username, the user is only allowed to
+        * login if the username is given in the same case as stored in the
+        * user database.
+        */
+       if (pw != NULL && strcmp(user, pw->pw_name) != 0) {
+               logit("Login name %.100s does not match stored username %.100s",
+                   user, pw->pw_name);
+               pw = NULL;
+       }
+#endif
        if (pw == NULL) {
                logit("Invalid user %.100s from %.100s",
                    user, get_remote_ipaddr());
@@ -560,6 +613,35 @@ getpwnamallow(const char *user)
        return (NULL);
 }
 
+/* Returns 1 if key is revoked by revoked_keys_file, 0 otherwise */
+int
+auth_key_is_revoked(Key *key)
+{
+       char *key_fp;
+
+       if (options.revoked_keys_file == NULL)
+               return 0;
+
+       switch (key_in_file(key, options.revoked_keys_file, 0)) {
+       case 0:
+               /* key not revoked */
+               return 0;
+       case -1:
+               /* Error opening revoked_keys_file: refuse all keys */
+               error("Revoked keys file is unreadable: refusing public key "
+                   "authentication");
+               return 1;
+       case 1:
+               /* Key revoked */
+               key_fp = key_fingerprint(key, SSH_FP_MD5, SSH_FP_HEX);
+               error("WARNING: authentication attempt with a revoked "
+                   "%s key %s ", key_type(key), key_fp);
+               xfree(key_fp);
+               return 1;
+       }
+       fatal("key_in_file returned junk");
+}
+
 void
 auth_debug_add(const char *fmt,...)
 {
index 3a70f44..77317ae 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: auth.h,v 1.62 2008/11/04 08:22:12 djm Exp $ */
+/* $OpenBSD: auth.h,v 1.66 2010/05/07 11:30:29 djm Exp $ */
 
 /*
  * Copyright (c) 2000 Markus Friedl.  All rights reserved.
@@ -169,8 +169,11 @@ void       abandon_challenge_response(Authctxt *);
 
 char   *authorized_keys_file(struct passwd *);
 char   *authorized_keys_file2(struct passwd *);
+char   *authorized_principals_file(struct passwd *);
 
 FILE   *auth_openkeyfile(const char *, struct passwd *, int);
+FILE   *auth_openprincipals(const char *, struct passwd *, int);
+int     auth_key_is_revoked(Key *);
 
 HostStatus
 check_key_in_hostfiles(struct passwd *, Key *, const char *,
@@ -178,7 +181,8 @@ check_key_in_hostfiles(struct passwd *, Key *, const char *,
 
 /* hostkey handling */
 Key    *get_hostkey_by_index(int);
-Key    *get_hostkey_by_type(int);
+Key    *get_hostkey_public_by_type(int);
+Key    *get_hostkey_private_by_type(int);
 int     get_hostkey_index(Key *);
 int     ssh1_session_key(BIGNUM *);
 
index 1801661..bf442db 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: auth1.c,v 1.73 2008/07/04 23:30:16 djm Exp $ */
+/* $OpenBSD: auth1.c,v 1.74 2010/06/25 08:46:17 djm Exp $ */
 /*
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
  *                    All rights reserved
@@ -244,7 +244,7 @@ do_authloop(Authctxt *authctxt)
            authctxt->valid ? "" : "invalid user ", authctxt->user);
 
        /* If the user has no password, accept authentication immediately. */
-       if (options.password_authentication &&
+       if (options.permit_empty_passwd && options.password_authentication &&
 #ifdef KRB5
            (!options.kerberos_authentication || options.kerberos_or_local_passwd) &&
 #endif
index 041051c..cdf442f 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: auth2-hostbased.c,v 1.12 2008/07/17 08:51:07 djm Exp $ */
+/* $OpenBSD: auth2-hostbased.c,v 1.14 2010/08/04 05:42:47 djm Exp $ */
 /*
  * Copyright (c) 2000 Markus Friedl.  All rights reserved.
  *
@@ -141,9 +141,13 @@ int
 hostbased_key_allowed(struct passwd *pw, const char *cuser, char *chost,
     Key *key)
 {
-       const char *resolvedname, *ipaddr, *lookup;
+       const char *resolvedname, *ipaddr, *lookup, *reason;
        HostStatus host_status;
        int len;
+       char *fp;
+
+       if (auth_key_is_revoked(key))
+               return 0;
 
        resolvedname = get_canonical_hostname(options.use_dns);
        ipaddr = get_remote_ipaddr();
@@ -171,16 +175,40 @@ hostbased_key_allowed(struct passwd *pw, const char *cuser, char *chost,
        }
        debug2("userauth_hostbased: access allowed by auth_rhosts2");
 
+       if (key_is_cert(key) && 
+           key_cert_check_authority(key, 1, 0, lookup, &reason)) {
+               error("%s", reason);
+               auth_debug_add("%s", reason);
+               return 0;
+       }
+
        host_status = check_key_in_hostfiles(pw, key, lookup,
            _PATH_SSH_SYSTEM_HOSTFILE,
            options.ignore_user_known_hosts ? NULL : _PATH_SSH_USER_HOSTFILE);
 
        /* backward compat if no key has been found. */
-       if (host_status == HOST_NEW)
+       if (host_status == HOST_NEW) {
                host_status = check_key_in_hostfiles(pw, key, lookup,
                    _PATH_SSH_SYSTEM_HOSTFILE2,
                    options.ignore_user_known_hosts ? NULL :
                    _PATH_SSH_USER_HOSTFILE2);
+       }
+
+       if (host_status == HOST_OK) {
+               if (key_is_cert(key)) {
+                       fp = key_fingerprint(key->cert->signature_key,
+                           SSH_FP_MD5, SSH_FP_HEX);
+                       verbose("Accepted certificate ID \"%s\" signed by "
+                           "%s CA %s from %s@%s", key->cert->key_id,
+                           key_type(key->cert->signature_key), fp,
+                           cuser, lookup);
+               } else {
+                       fp = key_fingerprint(key, SSH_FP_MD5, SSH_FP_HEX);
+                       verbose("Accepted %s public key %s from %s@%s",
+                           key_type(key), fp, cuser, lookup);
+               }
+               xfree(fp);
+       }
 
        return (host_status == HOST_OK);
 }
index 08f2f93..c8c6c74 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: auth2-none.c,v 1.15 2008/07/02 12:36:39 djm Exp $ */
+/* $OpenBSD: auth2-none.c,v 1.16 2010/06/25 08:46:17 djm Exp $ */
 /*
  * Copyright (c) 2000 Markus Friedl.  All rights reserved.
  *
@@ -61,7 +61,7 @@ userauth_none(Authctxt *authctxt)
 {
        none_enabled = 0;
        packet_check_eom();
-       if (options.password_authentication)
+       if (options.permit_empty_passwd && options.password_authentication)
                return (PRIVSEP(auth_password(authctxt, "")));
        return (0);
 }
index 2886f12..35cf79c 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: auth2-pubkey.c,v 1.19 2008/07/03 21:46:58 otto Exp $ */
+/* $OpenBSD: auth2-pubkey.c,v 1.26 2010/06/29 23:16:46 djm Exp $ */
 /*
  * Copyright (c) 2000 Markus Friedl.  All rights reserved.
  *
@@ -32,6 +32,8 @@
 #include <pwd.h>
 #include <stdio.h>
 #include <stdarg.h>
+#include <string.h>
+#include <time.h>
 #include <unistd.h>
 
 #include "xmalloc.h"
@@ -54,6 +56,8 @@
 #endif
 #include "monitor_wrap.h"
 #include "misc.h"
+#include "authfile.h"
+#include "match.h"
 
 /* import */
 extern ServerOptions options;
@@ -173,11 +177,89 @@ done:
        return authenticated;
 }
 
+static int
+match_principals_option(const char *principal_list, struct KeyCert *cert)
+{
+       char *result;
+       u_int i;
+
+       /* XXX percent_expand() sequences for authorized_principals? */
+
+       for (i = 0; i < cert->nprincipals; i++) {
+               if ((result = match_list(cert->principals[i],
+                   principal_list, NULL)) != NULL) {
+                       debug3("matched principal from key options \"%.100s\"",
+                           result);
+                       xfree(result);
+                       return 1;
+               }
+       }
+       return 0;
+}
+
+static int
+match_principals_file(char *file, struct passwd *pw, struct KeyCert *cert)
+{
+       FILE *f;
+       char line[SSH_MAX_PUBKEY_BYTES], *cp, *ep, *line_opts;
+       u_long linenum = 0;
+       u_int i;
+
+       temporarily_use_uid(pw);
+       debug("trying authorized principals file %s", file);
+       if ((f = auth_openprincipals(file, pw, options.strict_modes)) == NULL) {
+               restore_uid();
+               return 0;
+       }
+       while (read_keyfile_line(f, file, line, sizeof(line), &linenum) != -1) {
+               /* Skip leading whitespace. */
+               for (cp = line; *cp == ' ' || *cp == '\t'; cp++)
+                       ;
+               /* Skip blank and comment lines. */
+               if ((ep = strchr(cp, '#')) != NULL)
+                       *ep = '\0';
+               if (!*cp || *cp == '\n')
+                       continue;
+               /* Trim trailing whitespace. */
+               ep = cp + strlen(cp) - 1;
+               while (ep > cp && (*ep == '\n' || *ep == ' ' || *ep == '\t'))
+                       *ep-- = '\0';
+               /*
+                * If the line has internal whitespace then assume it has
+                * key options.
+                */
+               line_opts = NULL;
+               if ((ep = strrchr(cp, ' ')) != NULL ||
+                   (ep = strrchr(cp, '\t')) != NULL) {
+                       for (; *ep == ' ' || *ep == '\t'; ep++)
+                               ;;
+                       line_opts = cp;
+                       cp = ep;
+               }
+               for (i = 0; i < cert->nprincipals; i++) {
+                       if (strcmp(cp, cert->principals[i]) == 0) {
+                               debug3("matched principal from file \"%.100s\"",
+                                   cert->principals[i]);
+                               if (auth_parse_options(pw, line_opts,
+                                   file, linenum) != 1)
+                                       continue;
+                               fclose(f);
+                               restore_uid();
+                               return 1;
+                       }
+               }
+       }
+       fclose(f);
+       restore_uid();
+       return 0;
+}      
+
 /* return 1 if user allows given key */
 static int
 user_key_allowed2(struct passwd *pw, Key *key, char *file)
 {
        char line[SSH_MAX_PUBKEY_BYTES];
+       const char *reason;
        int found_key = 0;
        FILE *f;
        u_long linenum = 0;
@@ -196,11 +278,13 @@ user_key_allowed2(struct passwd *pw, Key *key, char *file)
        }
 
        found_key = 0;
-       found = key_new(key->type);
+       found = key_new(key_is_cert(key) ? KEY_UNSPEC : key->type);
 
        while (read_keyfile_line(f, file, line, sizeof(line), &linenum) != -1) {
                char *cp, *key_options = NULL;
 
+               auth_clear_options();
+
                /* Skip leading whitespace, empty and comment lines. */
                for (cp = line; *cp == ' ' || *cp == '\t'; cp++)
                        ;
@@ -227,8 +311,54 @@ user_key_allowed2(struct passwd *pw, Key *key, char *file)
                                continue;
                        }
                }
-               if (key_equal(found, key) &&
-                   auth_parse_options(pw, key_options, file, linenum) == 1) {
+               if (key_is_cert(key)) {
+                       if (!key_equal(found, key->cert->signature_key))
+                               continue;
+                       if (auth_parse_options(pw, key_options, file,
+                           linenum) != 1)
+                               continue;
+                       if (!key_is_cert_authority)
+                               continue;
+                       fp = key_fingerprint(found, SSH_FP_MD5,
+                           SSH_FP_HEX);
+                       debug("matching CA found: file %s, line %lu, %s %s",
+                           file, linenum, key_type(found), fp);
+                       /*
+                        * If the user has specified a list of principals as
+                        * a key option, then prefer that list to matching
+                        * their username in the certificate principals list.
+                        */
+                       if (authorized_principals != NULL &&
+                           !match_principals_option(authorized_principals,
+                           key->cert)) {
+                               reason = "Certificate does not contain an "
+                                   "authorized principal";
+ fail_reason:
+                               xfree(fp);
+                               error("%s", reason);
+                               auth_debug_add("%s", reason);
+                               continue;
+                       }
+                       if (key_cert_check_authority(key, 0, 0,
+                           authorized_principals == NULL ? pw->pw_name : NULL,
+                           &reason) != 0)
+                               goto fail_reason;
+                       if (auth_cert_options(key, pw) != 0) {
+                               xfree(fp);
+                               continue;
+                       }
+                       verbose("Accepted certificate ID \"%s\" "
+                           "signed by %s CA %s via %s", key->cert->key_id,
+                           key_type(found), fp, file);
+                       xfree(fp);
+                       found_key = 1;
+                       break;
+               } else if (key_equal(found, key)) {
+                       if (auth_parse_options(pw, key_options, file,
+                           linenum) != 1)
+                               continue;
+                       if (key_is_cert_authority)
+                               continue;
                        found_key = 1;
                        debug("matching key found: file %s, line %lu",
                            file, linenum);
@@ -247,6 +377,61 @@ user_key_allowed2(struct passwd *pw, Key *key, char *file)
        return found_key;
 }
 
+/* Authenticate a certificate key against TrustedUserCAKeys */
+static int
+user_cert_trusted_ca(struct passwd *pw, Key *key)
+{
+       char *ca_fp, *principals_file = NULL;
+       const char *reason;
+       int ret = 0;
+
+       if (!key_is_cert(key) || options.trusted_user_ca_keys == NULL)
+               return 0;
+
+       ca_fp = key_fingerprint(key->cert->signature_key,
+           SSH_FP_MD5, SSH_FP_HEX);
+
+       if (key_in_file(key->cert->signature_key,
+           options.trusted_user_ca_keys, 1) != 1) {
+               debug2("%s: CA %s %s is not listed in %s", __func__,
+                   key_type(key->cert->signature_key), ca_fp,
+                   options.trusted_user_ca_keys);
+               goto out;
+       }
+       /*
+        * If AuthorizedPrincipals is in use, then compare the certificate
+        * principals against the names in that file rather than matching
+        * against the username.
+        */
+       if ((principals_file = authorized_principals_file(pw)) != NULL) {
+               if (!match_principals_file(principals_file, pw, key->cert)) {
+                       reason = "Certificate does not contain an "
+                           "authorized principal";
+ fail_reason:
+                       error("%s", reason);
+                       auth_debug_add("%s", reason);
+                       goto out;
+               }
+       }
+       if (key_cert_check_authority(key, 0, 1,
+           principals_file == NULL ? pw->pw_name : NULL, &reason) != 0)
+               goto fail_reason;
+       if (auth_cert_options(key, pw) != 0)
+               goto out;
+
+       verbose("Accepted certificate ID \"%s\" signed by %s CA %s via %s",
+           key->cert->key_id, key_type(key->cert->signature_key), ca_fp,
+           options.trusted_user_ca_keys);
+       ret = 1;
+
+ out:
+       if (principals_file != NULL)
+               xfree(principals_file);
+       if (ca_fp != NULL)
+               xfree(ca_fp);
+       return ret;
+}
+
 /* check whether given key is in .ssh/authorized_keys* */
 int
 user_key_allowed(struct passwd *pw, Key *key)
@@ -254,6 +439,15 @@ user_key_allowed(struct passwd *pw, Key *key)
        int success;
        char *file;
 
+       if (auth_key_is_revoked(key))
+               return 0;
+       if (key_is_cert(key) && auth_key_is_revoked(key->cert->signature_key))
+               return 0;
+
+       success = user_cert_trusted_ca(pw, key);
+       if (success)
+               return success;
+
        file = authorized_keys_file(pw);
        success = user_key_allowed2(pw, key, file);
        xfree(file);
index 61faad1..739722f 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: authfd.c,v 1.80 2006/08/03 03:34:41 deraadt Exp $ */
+/* $OpenBSD: authfd.c,v 1.83 2010/04/16 01:47:26 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -483,6 +483,17 @@ ssh_encode_identity_ssh2(Buffer *b, Key *key, const char *comment)
                buffer_put_bignum2(b, key->rsa->p);
                buffer_put_bignum2(b, key->rsa->q);
                break;
+       case KEY_RSA_CERT_V00:
+       case KEY_RSA_CERT:
+               if (key->cert == NULL || buffer_len(&key->cert->certblob) == 0)
+                       fatal("%s: no cert/certblob", __func__);
+               buffer_put_string(b, buffer_ptr(&key->cert->certblob),
+                   buffer_len(&key->cert->certblob));
+               buffer_put_bignum2(b, key->rsa->d);
+               buffer_put_bignum2(b, key->rsa->iqmp);
+               buffer_put_bignum2(b, key->rsa->p);
+               buffer_put_bignum2(b, key->rsa->q);
+               break;
        case KEY_DSA:
                buffer_put_bignum2(b, key->dsa->p);
                buffer_put_bignum2(b, key->dsa->q);
@@ -490,6 +501,14 @@ ssh_encode_identity_ssh2(Buffer *b, Key *key, const char *comment)
                buffer_put_bignum2(b, key->dsa->pub_key);
                buffer_put_bignum2(b, key->dsa->priv_key);
                break;
+       case KEY_DSA_CERT_V00:
+       case KEY_DSA_CERT:
+               if (key->cert == NULL || buffer_len(&key->cert->certblob) == 0)
+                       fatal("%s: no cert/certblob", __func__);
+               buffer_put_string(b, buffer_ptr(&key->cert->certblob),
+                   buffer_len(&key->cert->certblob));
+               buffer_put_bignum2(b, key->dsa->priv_key);
+               break;
        }
        buffer_put_cstring(b, comment);
 }
@@ -517,7 +536,11 @@ ssh_add_identity_constrained(AuthenticationConnection *auth, Key *key,
                ssh_encode_identity_rsa1(&msg, key->rsa, comment);
                break;
        case KEY_RSA:
+       case KEY_RSA_CERT:
+       case KEY_RSA_CERT_V00:
        case KEY_DSA:
+       case KEY_DSA_CERT:
+       case KEY_DSA_CERT_V00:
                type = constrained ?
                    SSH2_AGENTC_ADD_ID_CONSTRAINED :
                    SSH2_AGENTC_ADD_IDENTITY;
@@ -545,12 +568,6 @@ ssh_add_identity_constrained(AuthenticationConnection *auth, Key *key,
        return decode_reply(type);
 }
 
-int
-ssh_add_identity(AuthenticationConnection *auth, Key *key, const char *comment)
-{
-       return ssh_add_identity_constrained(auth, key, comment, 0, 0);
-}
-
 /*
  * Removes an identity from the authentication server.  This call is not
  * meant to be used by normal applications.
@@ -571,7 +588,8 @@ ssh_remove_identity(AuthenticationConnection *auth, Key *key)
                buffer_put_int(&msg, BN_num_bits(key->rsa->n));
                buffer_put_bignum(&msg, key->rsa->e);
                buffer_put_bignum(&msg, key->rsa->n);
-       } else if (key->type == KEY_DSA || key->type == KEY_RSA) {
+       } else if (key_type_plain(key->type) == KEY_DSA ||
+           key_type_plain(key->type) == KEY_RSA) {
                key_to_blob(key, &blob, &blen);
                buffer_put_char(&msg, SSH2_AGENTC_REMOVE_IDENTITY);
                buffer_put_string(&msg, blob, blen);
index 3da2561..2582a27 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: authfd.h,v 1.36 2006/08/03 03:34:41 deraadt Exp $ */
+/* $OpenBSD: authfd.h,v 1.37 2009/08/27 17:44:52 djm Exp $ */
 
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -75,7 +75,6 @@ void  ssh_close_authentication_connection(AuthenticationConnection *);
 int     ssh_get_num_identities(AuthenticationConnection *, int);
 Key    *ssh_get_first_identity(AuthenticationConnection *, char **, int);
 Key    *ssh_get_next_identity(AuthenticationConnection *, char **, int);
-int     ssh_add_identity(AuthenticationConnection *, Key *, const char *);
 int     ssh_add_identity_constrained(AuthenticationConnection *, Key *,
     const char *, u_int, u_int);
 int     ssh_remove_identity(AuthenticationConnection *, Key *);
index 735c647..2bd8878 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: authfile.c,v 1.76 2006/08/03 03:34:41 deraadt Exp $ */
+/* $OpenBSD: authfile.c,v 1.82 2010/08/04 05:49:22 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -47,6 +47,9 @@
 #include <openssl/evp.h>
 #include <openssl/pem.h>
 
+/* compatibility with old or broken OpenSSL versions */
+#include "openbsd-compat/openssl-compat.h"
+
 #include <errno.h>
 #include <fcntl.h>
 #include <stdarg.h>
@@ -184,7 +187,11 @@ key_save_private_pem(Key *key, const char *filename, const char *_passphrase,
        int success = 0;
        int len = strlen(_passphrase);
        u_char *passphrase = (len > 0) ? (u_char *)_passphrase : NULL;
+#if (OPENSSL_VERSION_NUMBER < 0x00907000L)
        const EVP_CIPHER *cipher = (len > 0) ? EVP_des_ede3_cbc() : NULL;
+#else
+       const EVP_CIPHER *cipher = (len > 0) ? EVP_aes_128_cbc() : NULL;
+#endif
 
        if (len > 0 && len <= 4) {
                error("passphrase too short: have %d bytes, need > 4", len);
@@ -552,8 +559,13 @@ key_load_private_type(int type, const char *filename, const char *passphrase,
        int fd;
 
        fd = open(filename, O_RDONLY);
-       if (fd < 0)
+       if (fd < 0) {
+               debug("could not open key file '%s': %s", filename,
+                   strerror(errno));
+               if (perm_ok != NULL)
+                       *perm_ok = 0;
                return NULL;
+       }
        if (!key_perm_ok(fd, filename)) {
                if (perm_ok != NULL)
                        *perm_ok = 0;
@@ -588,8 +600,11 @@ key_load_private(const char *filename, const char *passphrase,
        int fd;
 
        fd = open(filename, O_RDONLY);
-       if (fd < 0)
+       if (fd < 0) {
+               debug("could not open key file '%s': %s", filename,
+                   strerror(errno));
                return NULL;
+       }
        if (!key_perm_ok(fd, filename)) {
                error("bad permissions: ignore key: %s", filename);
                close(fd);
@@ -677,3 +692,125 @@ key_load_public(const char *filename, char **commentp)
        key_free(pub);
        return NULL;
 }
+
+/* Load the certificate associated with the named private key */
+Key *
+key_load_cert(const char *filename)
+{
+       Key *pub;
+       char *file;
+
+       pub = key_new(KEY_UNSPEC);
+       xasprintf(&file, "%s-cert.pub", filename);
+       if (key_try_load_public(pub, file, NULL) == 1) {
+               xfree(file);
+               return pub;
+       }
+       xfree(file);
+       key_free(pub);
+       return NULL;
+}
+
+/* Load private key and certificate */
+Key *
+key_load_private_cert(int type, const char *filename, const char *passphrase,
+    int *perm_ok)
+{
+       Key *key, *pub;
+
+       switch (type) {
+       case KEY_RSA:
+       case KEY_DSA:
+               break;
+       default:
+               error("%s: unsupported key type", __func__);
+               return NULL;
+       }
+
+       if ((key = key_load_private_type(type, filename, 
+           passphrase, NULL, perm_ok)) == NULL)
+               return NULL;
+
+       if ((pub = key_load_cert(filename)) == NULL) {
+               key_free(key);
+               return NULL;
+       }
+
+       /* Make sure the private key matches the certificate */
+       if (key_equal_public(key, pub) == 0) {
+               error("%s: certificate does not match private key %s",
+                   __func__, filename);
+       } else if (key_to_certified(key, key_cert_is_legacy(pub)) != 0) {
+               error("%s: key_to_certified failed", __func__);
+       } else {
+               key_cert_copy(pub, key);
+               key_free(pub);
+               return key;
+       }
+
+       key_free(key);
+       key_free(pub);
+       return NULL;
+}
+
+/*
+ * Returns 1 if the specified "key" is listed in the file "filename",
+ * 0 if the key is not listed or -1 on error.
+ * If strict_type is set then the key type must match exactly,
+ * otherwise a comparison that ignores certficiate data is performed.
+ */
+int
+key_in_file(Key *key, const char *filename, int strict_type)
+{
+       FILE *f;
+       char line[SSH_MAX_PUBKEY_BYTES];
+       char *cp;
+       u_long linenum = 0;
+       int ret = 0;
+       Key *pub;
+       int (*key_compare)(const Key *, const Key *) = strict_type ?
+           key_equal : key_equal_public;
+
+       if ((f = fopen(filename, "r")) == NULL) {
+               if (errno == ENOENT) {
+                       debug("%s: keyfile \"%s\" missing", __func__, filename);
+                       return 0;
+               } else {
+                       error("%s: could not open keyfile \"%s\": %s", __func__,
+                           filename, strerror(errno));
+                       return -1;
+               }
+       }
+
+       while (read_keyfile_line(f, filename, line, sizeof(line),
+                   &linenum) != -1) {
+               cp = line;
+
+               /* Skip leading whitespace. */
+               for (; *cp && (*cp == ' ' || *cp == '\t'); cp++)
+                       ;
+
+               /* Skip comments and empty lines */
+               switch (*cp) {
+               case '#':
+               case '\n':
+               case '\0':
+                       continue;
+               }
+
+               pub = key_new(KEY_UNSPEC);
+               if (key_read(pub, &cp) != 1) {
+                       key_free(pub);
+                       continue;
+               }
+               if (key_compare(key, pub)) {
+                       ret = 1;
+                       key_free(pub);
+                       break;
+               }
+               key_free(pub);
+       }
+       fclose(f);
+       return ret;
+}
+
index a6c7493..6745dc0 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: authfile.h,v 1.13 2006/04/25 08:02:27 dtucker Exp $ */
+/* $OpenBSD: authfile.h,v 1.15 2010/08/04 05:42:47 djm Exp $ */
 
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
 #define AUTHFILE_H
 
 int     key_save_private(Key *, const char *, const char *, const char *);
+Key    *key_load_cert(const char *);
 Key    *key_load_public(const char *, char **);
 Key    *key_load_public_type(int, const char *, char **);
 Key    *key_load_private(const char *, const char *, char **);
+Key    *key_load_private_cert(int, const char *, const char *, int *);
 Key    *key_load_private_type(int, const char *, const char *, char **, int *);
 Key    *key_load_private_pem(int, int, const char *, char **);
 int     key_perm_ok(int, const char *);
+int     key_in_file(Key *, const char *, int);
 
 #endif
index cd9a35d..854fd51 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: bufaux.c,v 1.46 2008/06/10 23:21:34 dtucker Exp $ */
+/* $OpenBSD: bufaux.c,v 1.49 2010/03/26 03:13:17 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -84,7 +84,8 @@ buffer_get_int_ret(u_int *ret, Buffer *buffer)
 
        if (buffer_get_ret(buffer, (char *) buf, 4) == -1)
                return (-1);
-       *ret = get_u32(buf);
+       if (ret != NULL)
+               *ret = get_u32(buf);
        return (0);
 }
 
@@ -106,7 +107,8 @@ buffer_get_int64_ret(u_int64_t *ret, Buffer *buffer)
 
        if (buffer_get_ret(buffer, (char *) buf, 8) == -1)
                return (-1);
-       *ret = get_u64(buf);
+       if (ret != NULL)
+               *ret = get_u64(buf);
        return (0);
 }
 
@@ -166,7 +168,10 @@ buffer_get_string_ret(Buffer *buffer, u_int *length_ptr)
        u_int len;
 
        /* Get the length. */
-       len = buffer_get_int(buffer);
+       if (buffer_get_int_ret(&len, buffer) != 0) {
+               error("buffer_get_string_ret: cannot extract length");
+               return (NULL);
+       }
        if (len > 256 * 1024) {
                error("buffer_get_string_ret: bad string length %u", len);
                return (NULL);
@@ -198,14 +203,17 @@ buffer_get_string(Buffer *buffer, u_int *length_ptr)
 }
 
 void *
-buffer_get_string_ptr(Buffer *buffer, u_int *length_ptr)
+buffer_get_string_ptr_ret(Buffer *buffer, u_int *length_ptr)
 {
        void *ptr;
        u_int len;
 
-       len = buffer_get_int(buffer);
-       if (len > 256 * 1024)
-               fatal("buffer_get_string_ptr: bad string length %u", len);
+       if (buffer_get_int_ret(&len, buffer) != 0)
+               return NULL;
+       if (len > 256 * 1024) {
+               error("buffer_get_string_ptr: bad string length %u", len);
+               return NULL;
+       }
        ptr = buffer_ptr(buffer);
        buffer_consume(buffer, len);
        if (length_ptr)
@@ -213,6 +221,16 @@ buffer_get_string_ptr(Buffer *buffer, u_int *length_ptr)
        return (ptr);
 }
 
+void *
+buffer_get_string_ptr(Buffer *buffer, u_int *length_ptr)
+{
+       void *ret;
+
+       if ((ret = buffer_get_string_ptr_ret(buffer, length_ptr)) == NULL)
+               fatal("buffer_get_string_ptr: buffer error");
+       return (ret);
+}
+
 /*
  * Stores and arbitrary binary string in the buffer.
  */
index e02e1e3..ae97003 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: buffer.c,v 1.31 2006/08/03 03:34:41 deraadt Exp $ */
+/* $OpenBSD: buffer.c,v 1.32 2010/02/09 03:56:28 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -160,7 +160,7 @@ buffer_check_alloc(Buffer *buffer, u_int len)
 /* Returns the number of bytes of data in the buffer. */
 
 u_int
-buffer_len(Buffer *buffer)
+buffer_len(const Buffer *buffer)
 {
        return buffer->end - buffer->offset;
 }
@@ -228,7 +228,7 @@ buffer_consume_end(Buffer *buffer, u_int bytes)
 /* Returns a pointer to the first used byte in the buffer. */
 
 void *
-buffer_ptr(Buffer *buffer)
+buffer_ptr(const Buffer *buffer)
 {
        return buffer->buf + buffer->offset;
 }
@@ -236,7 +236,7 @@ buffer_ptr(Buffer *buffer)
 /* Dumps the contents of the buffer to stderr. */
 
 void
-buffer_dump(Buffer *buffer)
+buffer_dump(const Buffer *buffer)
 {
        u_int i;
        u_char *ucp = buffer->buf;
index d0f354e..4ef4f80 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: buffer.h,v 1.17 2008/05/08 06:59:01 markus Exp $ */
+/* $OpenBSD: buffer.h,v 1.19 2010/02/09 03:56:28 djm Exp $ */
 
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -27,8 +27,8 @@ void   buffer_init(Buffer *);
 void    buffer_clear(Buffer *);
 void    buffer_free(Buffer *);
 
-u_int   buffer_len(Buffer *);
-void   *buffer_ptr(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);
@@ -40,7 +40,7 @@ void   buffer_get(Buffer *, void *, u_int);
 void    buffer_consume(Buffer *, u_int);
 void    buffer_consume_end(Buffer *, u_int);
 
-void     buffer_dump(Buffer *);
+void     buffer_dump(const Buffer *);
 
 int     buffer_get_ret(Buffer *, void *, u_int);
 int     buffer_consume_ret(Buffer *, u_int);
@@ -81,6 +81,7 @@ 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 *);
+void   *buffer_get_string_ptr_ret(Buffer *, u_int *);
 int    buffer_get_char_ret(char *, Buffer *);
 
 #endif                         /* BUFFER_H */
index 22b19bb..ef94d91 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: canohost.c,v 1.65 2009/05/27 06:31:25 andreas Exp $ */
+/* $OpenBSD: canohost.c,v 1.66 2010/01/13 01:20:20 dtucker Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -27,6 +27,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <stdarg.h>
+#include <unistd.h>
 
 #include "xmalloc.h"
 #include "packet.h"
@@ -301,9 +302,22 @@ get_local_ipaddr(int sock)
 }
 
 char *
-get_local_name(int sock)
+get_local_name(int fd)
 {
-       return get_socket_address(sock, 0, NI_NAMEREQD);
+       char *host, myname[NI_MAXHOST];
+
+       /* Assume we were passed a socket */
+       if ((host = get_socket_address(fd, 0, NI_NAMEREQD)) != NULL)
+               return host;
+
+       /* Handle the case where we were passed a pipe */
+       if (gethostname(myname, sizeof(myname)) == -1) {
+               verbose("get_local_name: gethostname: %s", strerror(errno));
+       } else {
+               host = xstrdup(myname);
+       }
+
+       return host;
 }
 
 void
index e8b8aa0..1cd5004 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: channels.c,v 1.296 2009/05/25 06:48:00 andreas Exp $ */
+/* $OpenBSD: channels.c,v 1.309 2010/08/05 13:08:42 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -53,6 +53,7 @@
 #include <arpa/inet.h>
 
 #include <errno.h>
+#include <fcntl.h>
 #include <netdb.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -113,10 +114,10 @@ typedef struct {
 } ForwardPermission;
 
 /* List of all permitted host/port pairs to connect by the user. */
-static ForwardPermission permitted_opens[SSH_MAX_FORWARDS_PER_DIRECTION];
+static ForwardPermission *permitted_opens = NULL;
 
 /* List of all permitted host/port pairs to connect by the admin. */
-static ForwardPermission permitted_adm_opens[SSH_MAX_FORWARDS_PER_DIRECTION];
+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;
@@ -228,12 +229,16 @@ channel_register_fds(Channel *c, int rfd, int wfd, int efd,
        channel_max_fd = MAX(channel_max_fd, wfd);
        channel_max_fd = MAX(channel_max_fd, efd);
 
-       /* XXX set close-on-exec -markus */
+       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->ctl_fd = -1; /* XXX: set elsewhere */
        c->efd = efd;
        c->extended_usage = extusage;
 
@@ -322,6 +327,11 @@ channel_new(char *ctype, int type, int rfd, int wfd, int efd,
        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;
@@ -363,11 +373,10 @@ channel_close_fd(int *fdp)
 static void
 channel_close_fds(Channel *c)
 {
-       debug3("channel %d: close_fds r %d w %d e %d c %d",
-           c->self, c->rfd, c->wfd, c->efd, c->ctl_fd);
+       debug3("channel %d: close_fds r %d w %d e %d",
+           c->self, c->rfd, c->wfd, c->efd);
 
        channel_close_fd(&c->sock);
-       channel_close_fd(&c->ctl_fd);
        channel_close_fd(&c->rfd);
        channel_close_fd(&c->wfd);
        channel_close_fd(&c->efd);
@@ -393,8 +402,6 @@ channel_free(Channel *c)
 
        if (c->sock != -1)
                shutdown(c->sock, SHUT_RDWR);
-       if (c->ctl_fd != -1)
-               shutdown(c->ctl_fd, SHUT_RDWR);
        channel_close_fds(c);
        buffer_free(&c->input);
        buffer_free(&c->output);
@@ -516,6 +523,7 @@ channel_still_open(void)
                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:
@@ -529,6 +537,7 @@ channel_still_open(void)
                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:
@@ -560,6 +569,8 @@ channel_find_open(void)
                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:
@@ -610,6 +621,8 @@ channel_open_message(void)
                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:
@@ -620,12 +633,12 @@ channel_open_message(void)
                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 cfd %d)\r\n",
+                           "  #%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_fd);
+                           c->rfd, c->wfd, c->ctl_chan);
                        buffer_append(&buffer, buf, strlen(buf));
                        continue;
                default:
@@ -691,7 +704,7 @@ channel_register_status_confirm(int id, channel_confirm_cb *cb,
 }
 
 void
-channel_register_open_confirm(int id, channel_callback_fn *fn, void *ctx)
+channel_register_open_confirm(int id, channel_open_fn *fn, void *ctx)
 {
        Channel *c = channel_lookup(id);
 
@@ -826,15 +839,13 @@ channel_pre_open(Channel *c, fd_set *readset, fd_set *writeset)
                if (c->extended_usage == CHAN_EXTENDED_WRITE &&
                    buffer_len(&c->extended) > 0)
                        FD_SET(c->efd, writeset);
-               else if (!(c->flags & CHAN_EOF_SENT) &&
-                   c->extended_usage == CHAN_EXTENDED_READ &&
+               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? */
-       if (compat20 && c->ctl_fd != -1 &&
-           c->istate == CHAN_INPUT_OPEN && c->ostate == CHAN_OUTPUT_OPEN)
-               FD_SET(c->ctl_fd, readset);
 }
 
 /* ARGSUSED */
@@ -906,7 +917,7 @@ x11_open_helper(Buffer *b)
        }
        /* Check if authentication data matches our fake data. */
        if (data_len != x11_fake_data_len ||
-           memcmp(ucp + 12 + ((proto_len + 3) & ~3),
+           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;
@@ -979,6 +990,28 @@ channel_pre_x11_open(Channel *c, fd_set *readset, fd_set *writeset)
        }
 }
 
+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
@@ -1210,6 +1243,30 @@ channel_decode_socks5(Channel *c, fd_set *readset, fd_set *writeset)
        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)
@@ -1219,7 +1276,6 @@ channel_pre_dynamic(Channel *c, fd_set *readset, fd_set *writeset)
        int ret;
 
        have = buffer_len(&c->input);
-       c->delayed = 0;
        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. */
@@ -1322,6 +1378,13 @@ port_open_helper(Channel *c, char *rtype)
        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,
@@ -1423,16 +1486,8 @@ channel_post_port_listener(Channel *c, fd_set *readset, fd_set *writeset)
                if (c->path != NULL)
                        nc->path = xstrdup(c->path);
 
-               if (nextstate == SSH_CHANNEL_DYNAMIC) {
-                       /*
-                        * do not call the channel_post handler until
-                        * this flag has been reset by a pre-handler.
-                        * otherwise the FD_ISSET calls might overflow
-                        */
-                       nc->delayed = 1;
-               } else {
+               if (nextstate != SSH_CHANNEL_DYNAMIC)
                        port_open_helper(nc, rtype);
-               }
        }
 }
 
@@ -1589,13 +1644,14 @@ channel_handle_wfd(Channel *c, fd_set *readset, fd_set *writeset)
 {
        struct termios tio;
        u_char *data = NULL, *buf;
-       u_int dlen;
+       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);
@@ -1614,7 +1670,6 @@ channel_handle_wfd(Channel *c, fd_set *readset, fd_set *writeset)
 
                if (c->datagram) {
                        /* ignore truncated writes, datagrams might get lost */
-                       c->local_consumed += dlen + 4;
                        len = write(c->wfd, buf, dlen);
                        xfree(data);
                        if (len < 0 && (errno == EINTR || errno == EAGAIN ||
@@ -1627,7 +1682,7 @@ channel_handle_wfd(Channel *c, fd_set *readset, fd_set *writeset)
                                        chan_write_failed(c);
                                return -1;
                        }
-                       return 1;
+                       goto out;
                }
 #ifdef _AIX
                /* XXX: Later AIX versions can't push as much data to tty */
@@ -1669,10 +1724,10 @@ channel_handle_wfd(Channel *c, fd_set *readset, fd_set *writeset)
                }
 #endif
                buffer_consume(&c->output, len);
-               if (compat20 && len > 0) {
-                       c->local_consumed += len;
-               }
        }
+ out:
+       if (compat20 && olen > 0)
+               c->local_consumed += olen - buffer_len(&c->output);
        return 1;
 }
 
@@ -1702,7 +1757,9 @@ channel_handle_efd(Channel *c, fd_set *readset, fd_set *writeset)
                                buffer_consume(&c->extended, len);
                                c->local_consumed += len;
                        }
-               } else if (c->extended_usage == CHAN_EXTENDED_READ &&
+               } 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",
@@ -1715,43 +1772,17 @@ channel_handle_efd(Channel *c, fd_set *readset, fd_set *writeset)
                                    c->self, c->efd);
                                channel_close_fd(&c->efd);
                        } else {
-                               buffer_append(&c->extended, buf, len);
+                               if (c->extended_usage == CHAN_EXTENDED_IGNORE) {
+                                       debug3("channel %d: discard efd",
+                                           c->self);
+                               } else
+                                       buffer_append(&c->extended, buf, len);
                        }
                }
        }
        return 1;
 }
 
-/* ARGSUSED */
-static int
-channel_handle_ctl(Channel *c, fd_set *readset, fd_set *writeset)
-{
-       char buf[16];
-       int len;
-
-       /* Monitor control fd to detect if the slave client exits */
-       if (c->ctl_fd != -1 && FD_ISSET(c->ctl_fd, readset)) {
-               len = read(c->ctl_fd, buf, sizeof(buf));
-               if (len < 0 &&
-                   (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK))
-                       return 1;
-               if (len <= 0) {
-                       debug2("channel %d: ctl read<=0", c->self);
-                       if (c->type != SSH_CHANNEL_OPEN) {
-                               debug2("channel %d: not open", c->self);
-                               chan_mark_dead(c);
-                               return -1;
-                       } else {
-                               chan_read_failed(c);
-                               chan_write_failed(c);
-                       }
-                       return -1;
-               } else
-                       fatal("%s: unexpected data on ctl fd", __func__);
-       }
-       return 1;
-}
-
 static int
 channel_check_window(Channel *c)
 {
@@ -1777,17 +1808,136 @@ channel_check_window(Channel *c)
 static void
 channel_post_open(Channel *c, fd_set *readset, fd_set *writeset)
 {
-       if (c->delayed)
-               return;
        channel_handle_rfd(c, readset, writeset);
        channel_handle_wfd(c, readset, writeset);
        if (!compat20)
                return;
        channel_handle_efd(c, readset, writeset);
-       channel_handle_ctl(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)
@@ -1816,6 +1966,8 @@ channel_handler_init_20(void)
        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;
@@ -1824,6 +1976,8 @@ channel_handler_init_20(void)
        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
@@ -1910,17 +2064,23 @@ static void
 channel_handler(chan_fn *ftab[], fd_set *readset, fd_set *writeset)
 {
        static int did_init = 0;
-       u_int i;
+       u_int i, oalloc;
        Channel *c;
 
        if (!did_init) {
                channel_handler_init();
                did_init = 1;
        }
-       for (i = 0; i < channels_alloc; i++) {
+       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);
@@ -2012,6 +2172,14 @@ channel_output_poll(void)
 
                                        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);
@@ -2097,7 +2265,7 @@ channel_input_data(int type, u_int32_t seq, void *ctxt)
 {
        int id;
        char *data;
-       u_int data_len;
+       u_int data_len, win_len;
        Channel *c;
 
        /* Get the channel number and verify it. */
@@ -2113,6 +2281,9 @@ channel_input_data(int type, u_int32_t seq, void *ctxt)
 
        /* 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.
@@ -2123,23 +2294,23 @@ channel_input_data(int type, u_int32_t seq, void *ctxt)
         */
        if (!compat13 && c->ostate != CHAN_OUTPUT_OPEN) {
                if (compat20) {
-                       c->local_window -= data_len;
-                       c->local_consumed += data_len;
+                       c->local_window -= win_len;
+                       c->local_consumed += win_len;
                }
                return;
        }
 
        if (compat20) {
-               if (data_len > c->local_maxpacket) {
+               if (win_len > c->local_maxpacket) {
                        logit("channel %d: rcvd big packet %d, maxpack %d",
-                           c->self, data_len, c->local_maxpacket);
+                           c->self, win_len, c->local_maxpacket);
                }
-               if (data_len > c->local_window) {
+               if (win_len > c->local_window) {
                        logit("channel %d: rcvd too much data %d, win %d",
-                           c->self, data_len, c->local_window);
+                           c->self, win_len, c->local_window);
                        return;
                }
-               c->local_window -= data_len;
+               c->local_window -= win_len;
        }
        if (c->datagram)
                buffer_put_string(&c->output, data, data_len);
@@ -2311,7 +2482,7 @@ channel_input_open_confirmation(int type, u_int32_t seq, void *ctxt)
                c->remote_maxpacket = packet_get_int();
                if (c->open_confirm) {
                        debug2("callback start");
-                       c->open_confirm(c->self, c->open_confirm_ctx);
+                       c->open_confirm(c->self, 1, c->open_confirm_ctx);
                        debug2("callback done");
                }
                debug2("channel %d: open confirm rwindow %u rmax %u", c->self,
@@ -2362,6 +2533,11 @@ channel_input_open_failure(int type, u_int32_t seq, void *ctxt)
                        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. */
@@ -2577,6 +2753,8 @@ channel_setup_fwd_listener(int type, const char *listen_addr,
                }
 
                channel_set_reuseaddr(sock);
+               if (ai->ai_family == AF_INET6)
+                       sock_set_v6only(sock);
 
                debug("Local forwarding listening on %s port %s.",
                    ntop, strport);
@@ -2678,10 +2856,6 @@ channel_request_remote_forwarding(const char *listen_host, u_short listen_port,
 {
        int type, success = 0;
 
-       /* Record locally that connection to this host/port is permitted. */
-       if (num_permitted_opens >= SSH_MAX_FORWARDS_PER_DIRECTION)
-               fatal("channel_request_remote_forwarding: too many forwards");
-
        /* Send the forward request to the remote side. */
        if (compat20) {
                const char *address_to_bind;
@@ -2731,6 +2905,9 @@ channel_request_remote_forwarding(const char *listen_host, u_short listen_port,
                }
        }
        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;
@@ -2828,10 +3005,10 @@ channel_permit_all_opens(void)
 void
 channel_add_permitted_opens(char *host, int port)
 {
-       if (num_permitted_opens >= SSH_MAX_FORWARDS_PER_DIRECTION)
-               fatal("channel_add_permitted_opens: too many forwards");
        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++;
@@ -2842,10 +3019,10 @@ channel_add_permitted_opens(char *host, int port)
 int
 channel_add_adm_permitted_opens(char *host, int port)
 {
-       if (num_adm_permitted_opens >= SSH_MAX_FORWARDS_PER_DIRECTION)
-               fatal("channel_add_adm_permitted_opens: too many forwards");
        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;
@@ -2860,6 +3037,10 @@ channel_clear_permitted_opens(void)
        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;
 }
 
@@ -2871,6 +3052,10 @@ channel_clear_adm_permitted_opens(void)
        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;
 }
 
@@ -3098,7 +3283,11 @@ x11_create_display_inet(int x11_display_offset, int x11_use_localhost,
                        sock = socket(ai->ai_family, ai->ai_socktype,
                            ai->ai_protocol);
                        if (sock < 0) {
-                               if ((errno != EINVAL) && (errno != EAFNOSUPPORT)) {
+                               if ((errno != EINVAL) && (errno != EAFNOSUPPORT)
+#ifdef EPFNOSUPPORT
+                                   && (errno != EPFNOSUPPORT)
+#endif 
+                                   ) {
                                        error("socket: %.100s", strerror(errno));
                                        freeaddrinfo(aitop);
                                        return -1;
@@ -3108,13 +3297,8 @@ x11_create_display_inet(int x11_display_offset, int x11_use_localhost,
                                        continue;
                                }
                        }
-#ifdef IPV6_V6ONLY
-                       if (ai->ai_family == AF_INET6) {
-                               int on = 1;
-                               if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)) < 0)
-                                       error("setsockopt IPV6_V6ONLY: %.100s", strerror(errno));
-                       }
-#endif
+                       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) {
index 1488ed7..0680ed0 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: channels.h,v 1.98 2009/02/12 03:00:56 djm Exp $ */
+/* $OpenBSD: channels.h,v 1.104 2010/05/14 23:29:23 djm Exp $ */
 
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
 #define SSH_CHANNEL_CONNECTING         12
 #define SSH_CHANNEL_DYNAMIC            13
 #define SSH_CHANNEL_ZOMBIE             14      /* Almost dead. */
-#define SSH_CHANNEL_MAX_TYPE           15
+#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 *);
@@ -81,6 +84,9 @@ struct channel_connect {
        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 */
@@ -92,12 +98,16 @@ struct Channel {
        int     wfd;            /* write fd */
        int     efd;            /* extended fd */
        int     sock;           /* sock fd */
-       int     ctl_fd;         /* control fd (client sharing) */
+       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;                /* fdset hack */
+       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
@@ -121,7 +131,7 @@ struct Channel {
        char   *ctype;          /* type */
 
        /* callback */
-       channel_callback_fn     *open_confirm;
+       channel_open_fn         *open_confirm;
        void                    *open_confirm_ctx;
        channel_callback_fn     *detach_user;
        int                     detach_close;
@@ -138,6 +148,11 @@ struct Channel {
 
        /* 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
@@ -168,6 +183,7 @@ struct Channel {
 #define CHAN_CLOSE_RCVD                        0x02
 #define CHAN_EOF_SENT                  0x04
 #define CHAN_EOF_RCVD                  0x08
+#define CHAN_LOCAL                     0x10
 
 #define CHAN_RBUF      16*1024
 
@@ -194,7 +210,7 @@ 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_callback_fn *, void *);
+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 *,
@@ -239,6 +255,7 @@ 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);
index 9a7dc0a..de79793 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: clientloop.c,v 1.213 2009/07/05 19:28:33 stevesk Exp $ */
+/* $OpenBSD: clientloop.c,v 1.222 2010/07/19 09:15:12 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -121,7 +121,7 @@ extern int stdin_null_flag;
 extern int no_shell_flag;
 
 /* Control socket */
-extern int muxserver_sock;
+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
@@ -130,6 +130,9 @@ extern int muxserver_sock;
  */
 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
@@ -142,8 +145,11 @@ 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. */
-static volatile sig_atomic_t quit_pending; /* Set non-zero to quit the loop. */
+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. */
@@ -152,15 +158,18 @@ 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 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 = 0; /* In SSH2: login session closed. */
+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;
@@ -246,10 +255,38 @@ get_current_time(void)
        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, char **_proto, char **_data)
+    u_int trusted, u_int timeout, char **_proto, char **_data)
 {
        char cmd[1024];
        char line[512];
@@ -259,6 +296,7 @@ client_x11_get_proto(const char *display, const char *xauth_path,
        int got_data = 0, generated = 0, do_unlink = 0, i;
        char *xauthdir, *xauthfile;
        struct stat st;
+       u_int now;
 
        xauthdir = xauthfile = NULL;
        *_proto = proto;
@@ -294,11 +332,18 @@ client_x11_get_proto(const char *display, const char *xauth_path,
                                    xauthdir);
                                snprintf(cmd, sizeof(cmd),
                                    "%s -f %s generate %s " SSH_X11_PROTO
-                                   " untrusted timeout 1200 2>" _PATH_DEVNULL,
-                                   xauth_path, xauthfile, display);
+                                   " 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;
+                               }
                        }
                }
 
@@ -519,6 +564,7 @@ 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. */
@@ -559,22 +605,30 @@ client_wait_until_can_do_something(fd_set **readsetp, fd_set **writesetp,
        if (packet_have_data_to_write())
                FD_SET(connection_out, *writesetp);
 
-       if (muxserver_sock != -1)
-               FD_SET(muxserver_sock, *readsetp);
-
        /*
         * Wait for something to happen.  This will suspend the process until
         * some selected descriptor can be read, written, or has some other
-        * event pending.
+        * event pending, or a timeout expires.
         */
 
-       if (options.server_alive_interval == 0 || !compat20)
+       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 = options.server_alive_interval;
+               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];
@@ -608,7 +662,7 @@ client_suspend_self(Buffer *bin, Buffer *bout, Buffer *berr)
                atomicio(vwrite, fileno(stderr), buffer_ptr(berr),
                    buffer_len(berr));
 
-       leave_raw_mode();
+       leave_raw_mode(force_tty_flag);
 
        /*
         * Free (and clear) the buffer to reduce the amount of data that gets
@@ -629,7 +683,7 @@ client_suspend_self(Buffer *bin, Buffer *bout, Buffer *berr)
        buffer_init(bout);
        buffer_init(berr);
 
-       enter_raw_mode();
+       enter_raw_mode(force_tty_flag);
 }
 
 static void
@@ -690,7 +744,7 @@ client_status_confirm(int type, Channel *c, void *ctx)
 
        /* XXX supress on mux _client_ quietmode */
        tochan = options.log_level >= SYSLOG_LEVEL_ERROR &&
-           c->ctl_fd != -1 && c->extended_usage == CHAN_EXTENDED_WRITE;
+           c->ctl_chan != -1 && c->extended_usage == CHAN_EXTENDED_WRITE;
 
        if (type == SSH2_MSG_CHANNEL_SUCCESS) {
                debug2("%s request accepted on channel %d",
@@ -772,7 +826,7 @@ process_cmdline(void)
        bzero(&fwd, sizeof(fwd));
        fwd.listen_host = fwd.connect_host = NULL;
 
-       leave_raw_mode();
+       leave_raw_mode(force_tty_flag);
        handler = signal(SIGINT, SIG_IGN);
        cmd = s = read_passphrase("\r\nssh> ", RP_ECHO);
        if (s == NULL)
@@ -834,6 +888,7 @@ process_cmdline(void)
        while (isspace(*++s))
                ;
 
+       /* XXX update list of forwards in options */
        if (delete) {
                cancel_port = 0;
                cancel_host = hpdelim(&s);      /* may be NULL */
@@ -875,7 +930,7 @@ process_cmdline(void)
 
 out:
        signal(SIGINT, handler);
-       enter_raw_mode();
+       enter_raw_mode(force_tty_flag);
        if (cmd)
                xfree(cmd);
        if (fwd.listen_host != NULL)
@@ -931,7 +986,7 @@ process_escapes(Channel *c, Buffer *bin, Buffer *bout, Buffer *berr,
                                    escape_char);
                                buffer_append(berr, string, strlen(string));
 
-                               if (c && c->ctl_fd != -1) {
+                               if (c && c->ctl_chan != -1) {
                                        chan_read_failed(c);
                                        chan_write_failed(c);
                                        return 0;
@@ -941,7 +996,7 @@ process_escapes(Channel *c, Buffer *bin, Buffer *bout, Buffer *berr,
 
                        case 'Z' - 64:
                                /* XXX support this for mux clients */
-                               if (c && c->ctl_fd != -1) {
+                               if (c && c->ctl_chan != -1) {
  noescape:
                                        snprintf(string, sizeof string,
                                            "%c%c escape not available to "
@@ -986,7 +1041,7 @@ process_escapes(Channel *c, Buffer *bin, Buffer *bout, Buffer *berr,
                                continue;
 
                        case '&':
-                               if (c && c->ctl_fd != -1)
+                               if (c && c->ctl_chan != -1)
                                        goto noescape;
                                /*
                                 * Detach the program (continue to serve
@@ -994,7 +1049,7 @@ process_escapes(Channel *c, Buffer *bin, Buffer *bout, Buffer *berr,
                                 * more new connections).
                                 */
                                /* Restore tty modes. */
-                               leave_raw_mode();
+                               leave_raw_mode(force_tty_flag);
 
                                /* Stop listening for new connections. */
                                channel_stop_listening();
@@ -1037,7 +1092,7 @@ process_escapes(Channel *c, Buffer *bin, Buffer *bout, Buffer *berr,
                                continue;
 
                        case '?':
-                               if (c && c->ctl_fd != -1) {
+                               if (c && c->ctl_chan != -1) {
                                        snprintf(string, sizeof string,
 "%c?\r\n\
 Supported escape sequences:\r\n\
@@ -1086,7 +1141,7 @@ Supported escape sequences:\r\n\
                                continue;
 
                        case 'C':
-                               if (c && c->ctl_fd != -1)
+                               if (c && c->ctl_chan != -1)
                                        goto noescape;
                                process_cmdline();
                                continue;
@@ -1289,7 +1344,7 @@ client_channel_closed(int id, void *arg)
 {
        channel_cancel_cleanup(id);
        session_closed = 1;
-       leave_raw_mode();
+       leave_raw_mode(force_tty_flag);
 }
 
 /*
@@ -1322,8 +1377,6 @@ client_loop(int have_pty, int escape_char_arg, int ssh2_chan_id)
        connection_in = packet_get_connection_in();
        connection_out = packet_get_connection_out();
        max_fd = MAX(connection_in, connection_out);
-       if (muxserver_sock != -1)
-               max_fd = MAX(max_fd, muxserver_sock);
 
        if (!compat20) {
                /* enable nonblocking unless tty */
@@ -1362,7 +1415,7 @@ client_loop(int have_pty, int escape_char_arg, int ssh2_chan_id)
        signal(SIGWINCH, window_change_handler);
 
        if (have_pty)
-               enter_raw_mode();
+               enter_raw_mode(force_tty_flag);
 
        if (compat20) {
                session_ident = ssh2_chan_id;
@@ -1441,12 +1494,6 @@ client_loop(int have_pty, int escape_char_arg, int ssh2_chan_id)
                /* Buffer input from the connection.  */
                client_process_net_input(readset);
 
-               /* Accept control connections.  */
-               if (muxserver_sock != -1 &&FD_ISSET(muxserver_sock, readset)) {
-                       if (muxserver_accept_control())
-                               quit_pending = 1;
-               }
-
                if (quit_pending)
                        break;
 
@@ -1460,12 +1507,32 @@ client_loop(int have_pty, int escape_char_arg, int ssh2_chan_id)
                        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);
@@ -1481,6 +1548,7 @@ client_loop(int have_pty, int escape_char_arg, int ssh2_chan_id)
                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();
        }
@@ -1488,7 +1556,7 @@ client_loop(int have_pty, int escape_char_arg, int ssh2_chan_id)
        channel_free_all();
 
        if (have_pty)
-               leave_raw_mode();
+               leave_raw_mode(force_tty_flag);
 
        /* restore blocking io */
        if (!isatty(fileno(stdin)))
@@ -1682,6 +1750,11 @@ client_request_x11(const char *request_type, int rchan)
                    "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");
@@ -1846,15 +1919,17 @@ client_input_channel_req(int type, u_int32_t seq, void *ctxt)
                chan_rcvd_eow(c);
        } else if (strcmp(rtype, "exit-status") == 0) {
                exitval = packet_get_int();
-               if (id == session_ident) {
+               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 if (c->ctl_fd == -1) {
-                       error("client_input_channel_req: unexpected channel %d",
-                           session_ident);
                } else {
-                       atomicio(vwrite, c->ctl_fd, &exitval, sizeof(exitval));
-                       success = 1;
+                       /* Probably for a mux channel that has already closed */
+                       debug("%s: no sink for exit-status on channel %d",
+                           __func__, id);
                }
                packet_check_eom();
        }
@@ -1906,7 +1981,7 @@ client_session2_setup(int id, int want_tty, int want_subsystem,
                        memset(&ws, 0, sizeof(ws));
 
                channel_request_start(id, "pty-req", 1);
-               client_expect_confirm(id, "PTY allocation", 0);
+               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);
@@ -2050,7 +2125,7 @@ client_init_dispatch(void)
 void
 cleanup_exit(int i)
 {
-       leave_raw_mode();
+       leave_raw_mode(force_tty_flag);
        leave_non_blocking();
        if (options.control_path != NULL && muxserver_sock != -1)
                unlink(options.control_path);
index 8bb874b..52115db 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: clientloop.h,v 1.22 2008/06/12 15:19:17 djm Exp $ */
+/* $OpenBSD: clientloop.h,v 1.25 2010/06/25 23:15:36 djm Exp $ */
 
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -39,7 +39,7 @@
 
 /* Client side main loop for the interactive session. */
 int     client_loop(int, int, int);
-void    client_x11_get_proto(const char *, const char *, u_int,
+void    client_x11_get_proto(const char *, const char *, u_int, u_int,
            char **, char **);
 void    client_global_request_reply_fwd(int, u_int32_t, void *);
 void    client_session2_setup(int, int, int, const char *, struct termios *,
@@ -56,18 +56,15 @@ typedef void global_confirm_cb(int, u_int32_t seq, void *);
 void    client_register_global_confirm(global_confirm_cb *, void *);
 
 /* Multiplexing protocol version */
-#define SSHMUX_VER                     2
+#define SSHMUX_VER                     4
 
 /* Multiplexing control protocol flags */
 #define SSHMUX_COMMAND_OPEN            1       /* Open new connection */
 #define SSHMUX_COMMAND_ALIVE_CHECK     2       /* Check master is alive */
 #define SSHMUX_COMMAND_TERMINATE       3       /* Ask master to exit */
-
-#define SSHMUX_FLAG_TTY                        (1)     /* Request tty on open */
-#define SSHMUX_FLAG_SUBSYS             (1<<1)  /* Subsystem request on open */
-#define SSHMUX_FLAG_X11_FWD            (1<<2)  /* Request X11 forwarding */
-#define SSHMUX_FLAG_AGENT_FWD          (1<<3)  /* Request agent forwarding */
+#define SSHMUX_COMMAND_STDIO_FWD       4       /* Open stdio fwd (ssh -W) */
+#define SSHMUX_COMMAND_FORWARD         5       /* Forward only, no command */
 
 void   muxserver_listen(void);
-int    muxserver_accept_control(void);
 void   muxclient(const char *);
+void   mux_exit_message(Channel *, int);
index 2ddfd96..fe25170 100644 (file)
@@ -25,7 +25,7 @@
 #ifndef _DEFINES_H
 #define _DEFINES_H
 
-/* $Id: defines.h,v 1.156 2009/08/28 01:21:07 dtucker Exp $ */
+/* $Id: defines.h,v 1.160 2010/04/09 08:13:27 dtucker Exp $ */
 
 
 /* Constants */
@@ -674,7 +674,7 @@ struct winsize {
 #else
 /* Simply select your favourite login types. */
 /* Can't do if-else because some systems use several... <sigh> */
-#  if defined(UTMPX_FILE) && !defined(DISABLE_UTMPX)
+#  if !defined(DISABLE_UTMPX)
 #    define USE_UTMPX
 #  endif
 #  if defined(UTMP_FILE) && !defined(DISABLE_UTMP)
@@ -753,4 +753,12 @@ struct winsize {
 # define SSH_IOBUFSZ 8192
 #endif
 
+#ifndef _NSIG
+# ifdef NSIG
+#  define _NSIG NSIG
+# else
+#  define _NSIG 128
+# endif
+#endif
+
 #endif /* _DEFINES_H */
index b766053..b9029d8 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: dh.c,v 1.47 2008/06/26 09:19:39 djm Exp $ */
+/* $OpenBSD: dh.c,v 1.48 2009/10/01 11:37:33 grunk Exp $ */
 /*
  * Copyright (c) 2000 Niels Provos.  All rights reserved.
  *
@@ -83,7 +83,7 @@ parse_prime(int linenum, char *line, struct dhgroup *dhg)
                goto fail;
        strsize = strsep(&cp, " "); /* size */
        if (cp == NULL || *strsize == '\0' ||
-           (dhg->size = (u_int)strtonum(strsize, 0, 64*1024, &errstr)) == 0 ||
+           (dhg->size = (int)strtonum(strsize, 0, 64*1024, &errstr)) == 0 ||
            errstr)
                goto fail;
        /* The whole group is one bit larger */
index a7da03f..2e7bb5a 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: dns.c,v 1.25 2008/06/12 00:03:49 dtucker Exp $ */
+/* $OpenBSD: dns.c,v 1.26 2010/02/26 20:29:54 djm Exp $ */
 
 /*
  * Copyright (c) 2003 Wesley Griffin. All rights reserved.
@@ -75,7 +75,7 @@ dns_result_totext(unsigned int res)
  */
 static int
 dns_read_key(u_int8_t *algorithm, u_int8_t *digest_type,
-    u_char **digest, u_int *digest_len, const Key *key)
+    u_char **digest, u_int *digest_len, Key *key)
 {
        int success = 0;
 
@@ -172,7 +172,7 @@ is_numeric_hostname(const char *hostname)
  */
 int
 verify_host_key_dns(const char *hostname, struct sockaddr *address,
-    const Key *hostkey, int *flags)
+    Key *hostkey, int *flags)
 {
        u_int counter;
        int result;
@@ -271,7 +271,7 @@ verify_host_key_dns(const char *hostname, struct sockaddr *address,
  * Export the fingerprint of a key as a DNS resource record
  */
 int
-export_dns_rr(const char *hostname, const Key *key, FILE *f, int generic)
+export_dns_rr(const char *hostname, Key *key, FILE *f, int generic)
 {
        u_int8_t rdata_pubkey_algorithm = 0;
        u_int8_t rdata_digest_type = SSHFP_HASH_SHA1;
index b2633a1..90cfd7b 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: dns.h,v 1.10 2006/08/03 03:34:42 deraadt Exp $ */
+/* $OpenBSD: dns.h,v 1.11 2010/02/26 20:29:54 djm Exp $ */
 
 /*
  * Copyright (c) 2003 Wesley Griffin. All rights reserved.
@@ -46,7 +46,7 @@ enum sshfp_hashes {
 #define DNS_VERIFY_MATCH       0x00000002
 #define DNS_VERIFY_SECURE      0x00000004
 
-int    verify_host_key_dns(const char *, struct sockaddr *, const Key *, int *);
-int    export_dns_rr(const char *, const Key *, FILE *, int);
+int    verify_host_key_dns(const char *, struct sockaddr *, Key *, int *);
+int    export_dns_rr(const char *, Key *, FILE *, int);
 
 #endif /* DNS_H */
index 2cceb35..afab6da 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: hostfile.c,v 1.45 2006/08/03 03:34:42 deraadt Exp $ */
+/* $OpenBSD: hostfile.c,v 1.48 2010/03/04 10:36:03 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -183,6 +183,41 @@ hostfile_check_key(int bits, const Key *key, const char *host, const char *filen
        return 1;
 }
 
+static enum { MRK_ERROR, MRK_NONE, MRK_REVOKE, MRK_CA }
+check_markers(char **cpp)
+{
+       char marker[32], *sp, *cp = *cpp;
+       int ret = MRK_NONE;
+
+       while (*cp == '@') {
+               /* Only one marker is allowed */
+               if (ret != MRK_NONE)
+                       return MRK_ERROR;
+               /* Markers are terminated by whitespace */
+               if ((sp = strchr(cp, ' ')) == NULL &&
+                   (sp = strchr(cp, '\t')) == NULL)
+                       return MRK_ERROR;
+               /* Extract marker for comparison */
+               if (sp <= cp + 1 || sp >= cp + sizeof(marker))
+                       return MRK_ERROR;
+               memcpy(marker, cp, sp - cp);
+               marker[sp - cp] = '\0';
+               if (strcmp(marker, CA_MARKER) == 0)
+                       ret = MRK_CA;
+               else if (strcmp(marker, REVOKE_MARKER) == 0)
+                       ret = MRK_REVOKE;
+               else
+                       return MRK_ERROR;
+
+               /* Skip past marker and any whitespace that follows it */
+               cp = sp;
+               for (; *cp == ' ' || *cp == '\t'; cp++)
+                       ;
+       }
+       *cpp = cp;
+       return ret;
+}
+
 /*
  * Checks whether the given host (which must be in all lowercase) is already
  * in the list of our known hosts. Returns HOST_OK if the host is known and
@@ -195,16 +230,20 @@ hostfile_check_key(int bits, const Key *key, const char *host, const char *filen
 
 static HostStatus
 check_host_in_hostfile_by_key_or_type(const char *filename,
-    const char *host, const Key *key, int keytype, Key *found, int *numret)
+    const char *host, const Key *key, int keytype, Key *found,
+    int want_revocation, int *numret)
 {
        FILE *f;
        char line[8192];
-       int linenum = 0;
+       int want, have, linenum = 0, want_cert = key_is_cert(key);
        u_int kbits;
        char *cp, *cp2, *hashed_host;
        HostStatus end_return;
 
-       debug3("check_host_in_hostfile: filename %s", filename);
+       debug3("check_host_in_hostfile: host %s filename %s", host, filename);
+
+       if (want_revocation && (key == NULL || keytype != 0 || found != NULL))
+               fatal("%s: invalid arguments", __func__);
 
        /* Open the file containing the list of known hosts. */
        f = fopen(filename, "r");
@@ -229,6 +268,20 @@ check_host_in_hostfile_by_key_or_type(const char *filename,
                if (!*cp || *cp == '#' || *cp == '\n')
                        continue;
 
+               if (want_revocation)
+                       want = MRK_REVOKE;
+               else if (want_cert)
+                       want = MRK_CA;
+               else
+                       want = MRK_NONE;
+
+               if ((have = check_markers(&cp)) == MRK_ERROR) {
+                       verbose("%s: invalid marker at %s:%d",
+                           __func__, filename, linenum);
+                       continue;
+               } else if (want != have)
+                       continue;
+
                /* Find the end of the host name portion. */
                for (cp2 = cp; *cp2 && *cp2 != ' ' && *cp2 != '\t'; cp2++)
                        ;
@@ -250,6 +303,9 @@ check_host_in_hostfile_by_key_or_type(const char *filename,
                /* Got a match.  Skip host name. */
                cp = cp2;
 
+               if (want_revocation)
+                       found = key_new(KEY_UNSPEC);
+
                /*
                 * Extract the key from the line.  This will skip any leading
                 * whitespace.  Ignore badly formatted lines.
@@ -272,9 +328,33 @@ check_host_in_hostfile_by_key_or_type(const char *filename,
                if (!hostfile_check_key(kbits, found, host, filename, linenum))
                        continue;
 
+               if (want_revocation) {
+                       if (key_is_cert(key) &&
+                           key_equal_public(key->cert->signature_key, found)) {
+                               verbose("check_host_in_hostfile: revoked CA "
+                                   "line %d", linenum);
+                               key_free(found);
+                               return HOST_REVOKED;
+                       }
+                       if (key_equal_public(key, found)) {
+                               verbose("check_host_in_hostfile: revoked key "
+                                   "line %d", linenum);
+                               key_free(found);
+                               return HOST_REVOKED;
+                       }
+                       key_free(found);
+                       continue;
+               }
+
                /* Check if the current key is the same as the given key. */
-               if (key_equal(key, found)) {
-                       /* Ok, they match. */
+               if (want_cert && key_equal(key->cert->signature_key, found)) {
+                       /* Found CA cert for key */
+                       debug3("check_host_in_hostfile: CA match line %d",
+                           linenum);
+                       fclose(f);
+                       return HOST_OK;
+               } else if (!want_cert && key_equal(key, found)) {
+                       /* Found identical key */
                        debug3("check_host_in_hostfile: match line %d", linenum);
                        fclose(f);
                        return HOST_OK;
@@ -302,8 +382,11 @@ check_host_in_hostfile(const char *filename, const char *host, const Key *key,
 {
        if (key == NULL)
                fatal("no key to look up");
-       return (check_host_in_hostfile_by_key_or_type(filename, host, key, 0,
-           found, numret));
+       if (check_host_in_hostfile_by_key_or_type(filename, host,
+           key, 0, NULL, 1, NULL) == HOST_REVOKED)
+               return HOST_REVOKED;
+       return check_host_in_hostfile_by_key_or_type(filename, host, key, 0,
+           found, 0, numret);
 }
 
 int
@@ -311,7 +394,7 @@ lookup_key_in_hostfile_by_type(const char *filename, const char *host,
     int keytype, Key *found, int *numret)
 {
        return (check_host_in_hostfile_by_key_or_type(filename, host, NULL,
-           keytype, found, numret) == HOST_FOUND);
+           keytype, found, 0, numret) == HOST_FOUND);
 }
 
 /*
index d1983b3..1d460c1 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: hostfile.h,v 1.16 2006/03/25 22:22:43 djm Exp $ */
+/* $OpenBSD: hostfile.h,v 1.18 2010/03/04 10:36:03 djm Exp $ */
 
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -15,7 +15,7 @@
 #define HOSTFILE_H
 
 typedef enum {
-       HOST_OK, HOST_NEW, HOST_CHANGED, HOST_FOUND
+       HOST_OK, HOST_NEW, HOST_CHANGED, HOST_REVOKED, HOST_FOUND
 }       HostStatus;
 
 int     hostfile_read_key(char **, u_int *, Key *);
@@ -28,6 +28,9 @@ int   lookup_key_in_hostfile_by_type(const char *, const char *,
 #define HASH_MAGIC     "|1|"
 #define HASH_DELIM     '|'
 
+#define CA_MARKER      "@cert-authority"
+#define REVOKE_MARKER  "@revoked"
+
 char   *host_hash(const char *, const char *, u_int);
 
 #endif
index 1306610..cdf65f5 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: jpake.c,v 1.2 2009/03/05 07:18:19 djm Exp $ */
+/* $OpenBSD: jpake.c,v 1.4 2010/07/13 23:13:16 djm Exp $ */
 /*
  * Copyright (c) 2008 Damien Miller.  All rights reserved.
  *
@@ -434,7 +434,7 @@ jpake_check_confirm(const BIGNUM *k,
        if (peer_confirm_hash_len != expected_confirm_hash_len)
                error("%s: confirmation length mismatch (my %u them %u)",
                    __func__, expected_confirm_hash_len, peer_confirm_hash_len);
-       else if (memcmp(peer_confirm_hash, expected_confirm_hash,
+       else if (timingsafe_bcmp(peer_confirm_hash, expected_confirm_hash,
            expected_confirm_hash_len) == 0)
                success = 1;
        bzero(expected_confirm_hash, expected_confirm_hash_len);
index f4f44f0..148cfee 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: kex.c,v 1.81 2009/05/27 06:34:36 andreas Exp $ */
+/* $OpenBSD: kex.c,v 1.82 2009/10/24 11:13:54 andreas Exp $ */
 /*
  * Copyright (c) 2000, 2001 Markus Friedl.  All rights reserved.
  *
@@ -48,6 +48,7 @@
 #include "match.h"
 #include "dispatch.h"
 #include "monitor.h"
+#include "roaming.h"
 
 #if OPENSSL_VERSION_NUMBER >= 0x00907000L
 # if defined(HAVE_EVP_SHA256)
@@ -386,6 +387,16 @@ kex_choose_conf(Kex *kex)
                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 */
        for (mode = 0; mode < MODE_MAX; mode++) {
                newkeys = xcalloc(1, sizeof(*newkeys));
index 68c80c5..62fa2ea 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: kex.h,v 1.47 2009/05/27 06:34:36 andreas Exp $ */
+/* $OpenBSD: kex.h,v 1.49 2010/02/26 20:29:54 djm Exp $ */
 
 /*
  * Copyright (c) 2000, 2001 Markus Friedl.  All rights reserved.
@@ -36,6 +36,7 @@
 #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"
 
 #define COMP_NONE      0
 #define COMP_ZLIB      1
@@ -116,6 +117,7 @@ struct Kex {
        char    *name;
        int     hostkey_type;
        int     kex_type;
+       int     roaming;
        Buffer  my;
        Buffer  peer;
        sig_atomic_t done;
@@ -124,7 +126,8 @@ struct Kex {
        char    *client_version_string;
        char    *server_version_string;
        int     (*verify_host_key)(Key *);
-       Key     *(*load_host_key)(int);
+       Key     *(*load_host_public_key)(int);
+       Key     *(*load_host_private_key)(int);
        int     (*host_key_index)(Key *);
        void    (*kex[KEX_MAX])(Kex *);
 };
index a6719f6..e722877 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: kexdhs.c,v 1.10 2009/06/21 07:37:15 dtucker Exp $ */
+/* $OpenBSD: kexdhs.c,v 1.11 2010/02/26 20:29:54 djm Exp $ */
 /*
  * Copyright (c) 2001 Markus Friedl.  All rights reserved.
  *
@@ -50,7 +50,7 @@ kexdh_server(Kex *kex)
 {
        BIGNUM *shared_secret = NULL, *dh_client_pub = NULL;
        DH *dh;
-       Key *server_host_key;
+       Key *server_host_public, *server_host_private;
        u_char *kbuf, *hash, *signature = NULL, *server_host_key_blob = NULL;
        u_int sbloblen, klen, hashlen, slen;
        int kout;
@@ -71,11 +71,16 @@ kexdh_server(Kex *kex)
        debug("expecting SSH2_MSG_KEXDH_INIT");
        packet_read_expect(SSH2_MSG_KEXDH_INIT);
 
-       if (kex->load_host_key == NULL)
+       if (kex->load_host_public_key == NULL ||
+           kex->load_host_private_key == NULL)
                fatal("Cannot load hostkey");
-       server_host_key = kex->load_host_key(kex->hostkey_type);
-       if (server_host_key == NULL)
+       server_host_public = kex->load_host_public_key(kex->hostkey_type);
+       if (server_host_public == NULL)
                fatal("Unsupported hostkey type %d", kex->hostkey_type);
+       server_host_private = kex->load_host_private_key(kex->hostkey_type);
+       if (server_host_private == NULL)
+               fatal("Missing private key for hostkey type %d",
+                   kex->hostkey_type);
 
        /* key, cert */
        if ((dh_client_pub = BN_new()) == NULL)
@@ -113,7 +118,7 @@ kexdh_server(Kex *kex)
        memset(kbuf, 0, klen);
        xfree(kbuf);
 
-       key_to_blob(server_host_key, &server_host_key_blob, &sbloblen);
+       key_to_blob(server_host_public, &server_host_key_blob, &sbloblen);
 
        /* calc H */
        kex_dh_hash(
@@ -137,7 +142,7 @@ kexdh_server(Kex *kex)
        }
 
        /* sign H */
-       if (PRIVSEP(key_sign(server_host_key, &signature, &slen, hash,
+       if (PRIVSEP(key_sign(server_host_private, &signature, &slen, hash,
            hashlen)) < 0)
                fatal("kexdh_server: key_sign failed");
 
index 8515568..f4156af 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: kexgexs.c,v 1.12 2009/06/21 07:37:15 dtucker Exp $ */
+/* $OpenBSD: kexgexs.c,v 1.13 2010/02/26 20:29:54 djm Exp $ */
 /*
  * Copyright (c) 2000 Niels Provos.  All rights reserved.
  * Copyright (c) 2001 Markus Friedl.  All rights reserved.
@@ -52,18 +52,24 @@ void
 kexgex_server(Kex *kex)
 {
        BIGNUM *shared_secret = NULL, *dh_client_pub = NULL;
-       Key *server_host_key;
+       Key *server_host_public, *server_host_private;
        DH *dh;
        u_char *kbuf, *hash, *signature = NULL, *server_host_key_blob = NULL;
        u_int sbloblen, klen, slen, hashlen;
        int omin = -1, min = -1, omax = -1, max = -1, onbits = -1, nbits = -1;
        int type, kout;
 
-       if (kex->load_host_key == NULL)
+       if (kex->load_host_public_key == NULL ||
+           kex->load_host_private_key == NULL)
                fatal("Cannot load hostkey");
-       server_host_key = kex->load_host_key(kex->hostkey_type);
-       if (server_host_key == NULL)
+       server_host_public = kex->load_host_public_key(kex->hostkey_type);
+       if (server_host_public == NULL)
                fatal("Unsupported hostkey type %d", kex->hostkey_type);
+       server_host_private = kex->load_host_private_key(kex->hostkey_type);
+       if (server_host_private == NULL)
+               fatal("Missing private key for hostkey type %d",
+                   kex->hostkey_type);
+
 
        type = packet_read();
        switch (type) {
@@ -149,7 +155,7 @@ kexgex_server(Kex *kex)
        memset(kbuf, 0, klen);
        xfree(kbuf);
 
-       key_to_blob(server_host_key, &server_host_key_blob, &sbloblen);
+       key_to_blob(server_host_public, &server_host_key_blob, &sbloblen);
 
        if (type == SSH2_MSG_KEX_DH_GEX_REQUEST_OLD)
                omin = min = omax = max = -1;
@@ -179,7 +185,7 @@ kexgex_server(Kex *kex)
        }
 
        /* sign H */
-       if (PRIVSEP(key_sign(server_host_key, &signature, &slen, hash,
+       if (PRIVSEP(key_sign(server_host_private, &signature, &slen, hash,
            hashlen)) < 0)
                fatal("kexgex_server: key_sign failed");
 
index 3e17da6..e4aa25c 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: key.c,v 1.80 2008/10/10 05:00:12 stevesk Exp $ */
+/* $OpenBSD: key.c,v 1.90 2010/07/13 23:13:16 djm Exp $ */
 /*
  * read_bignum():
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
 #include "uuencode.h"
 #include "buffer.h"
 #include "log.h"
+#include "misc.h"
+#include "ssh2.h"
+
+static struct KeyCert *
+cert_new(void)
+{
+       struct KeyCert *cert;
+
+       cert = xcalloc(1, sizeof(*cert));
+       buffer_init(&cert->certblob);
+       buffer_init(&cert->critical);
+       buffer_init(&cert->extensions);
+       cert->key_id = NULL;
+       cert->principals = NULL;
+       cert->signature_key = NULL;
+       return cert;
+}
 
 Key *
 key_new(int type)
@@ -63,9 +80,12 @@ key_new(int type)
        k->type = type;
        k->dsa = NULL;
        k->rsa = NULL;
+       k->cert = NULL;
        switch (k->type) {
        case KEY_RSA1:
        case KEY_RSA:
+       case KEY_RSA_CERT_V00:
+       case KEY_RSA_CERT:
                if ((rsa = RSA_new()) == NULL)
                        fatal("key_new: RSA_new failed");
                if ((rsa->n = BN_new()) == NULL)
@@ -75,6 +95,8 @@ key_new(int type)
                k->rsa = rsa;
                break;
        case KEY_DSA:
+       case KEY_DSA_CERT_V00:
+       case KEY_DSA_CERT:
                if ((dsa = DSA_new()) == NULL)
                        fatal("key_new: DSA_new failed");
                if ((dsa->p = BN_new()) == NULL)
@@ -93,16 +115,21 @@ key_new(int type)
                fatal("key_new: bad key type %d", k->type);
                break;
        }
+
+       if (key_is_cert(k))
+               k->cert = cert_new();
+
        return k;
 }
 
-Key *
-key_new_private(int type)
+void
+key_add_private(Key *k)
 {
-       Key *k = key_new(type);
        switch (k->type) {
        case KEY_RSA1:
        case KEY_RSA:
+       case KEY_RSA_CERT_V00:
+       case KEY_RSA_CERT:
                if ((k->rsa->d = BN_new()) == NULL)
                        fatal("key_new_private: BN_new failed");
                if ((k->rsa->iqmp = BN_new()) == NULL)
@@ -117,6 +144,8 @@ key_new_private(int type)
                        fatal("key_new_private: BN_new failed");
                break;
        case KEY_DSA:
+       case KEY_DSA_CERT_V00:
+       case KEY_DSA_CERT:
                if ((k->dsa->priv_key = BN_new()) == NULL)
                        fatal("key_new_private: BN_new failed");
                break;
@@ -125,9 +154,35 @@ key_new_private(int type)
        default:
                break;
        }
+}
+
+Key *
+key_new_private(int type)
+{
+       Key *k = key_new(type);
+
+       key_add_private(k);
        return k;
 }
 
+static void
+cert_free(struct KeyCert *cert)
+{
+       u_int i;
+
+       buffer_free(&cert->certblob);
+       buffer_free(&cert->critical);
+       buffer_free(&cert->extensions);
+       if (cert->key_id != NULL)
+               xfree(cert->key_id);
+       for (i = 0; i < cert->nprincipals; i++)
+               xfree(cert->principals[i]);
+       if (cert->principals != NULL)
+               xfree(cert->principals);
+       if (cert->signature_key != NULL)
+               key_free(cert->signature_key);
+}
+
 void
 key_free(Key *k)
 {
@@ -136,11 +191,15 @@ key_free(Key *k)
        switch (k->type) {
        case KEY_RSA1:
        case KEY_RSA:
+       case KEY_RSA_CERT_V00:
+       case KEY_RSA_CERT:
                if (k->rsa != NULL)
                        RSA_free(k->rsa);
                k->rsa = NULL;
                break;
        case KEY_DSA:
+       case KEY_DSA_CERT_V00:
+       case KEY_DSA_CERT:
                if (k->dsa != NULL)
                        DSA_free(k->dsa);
                k->dsa = NULL;
@@ -151,20 +210,51 @@ key_free(Key *k)
                fatal("key_free: bad key type %d", k->type);
                break;
        }
+       if (key_is_cert(k)) {
+               if (k->cert != NULL)
+                       cert_free(k->cert);
+               k->cert = NULL;
+       }
+
        xfree(k);
 }
 
+static int
+cert_compare(struct KeyCert *a, struct KeyCert *b)
+{
+       if (a == NULL && b == NULL)
+               return 1;
+       if (a == NULL || b == NULL)
+               return 0;
+       if (buffer_len(&a->certblob) != buffer_len(&b->certblob))
+               return 0;
+       if (timingsafe_bcmp(buffer_ptr(&a->certblob), buffer_ptr(&b->certblob),
+           buffer_len(&a->certblob)) != 0)
+               return 0;
+       return 1;
+}
+
+/*
+ * Compare public portions of key only, allowing comparisons between
+ * certificates and plain keys too.
+ */
 int
-key_equal(const Key *a, const Key *b)
+key_equal_public(const Key *a, const Key *b)
 {
-       if (a == NULL || b == NULL || a->type != b->type)
+       if (a == NULL || b == NULL ||
+           key_type_plain(a->type) != key_type_plain(b->type))
                return 0;
+
        switch (a->type) {
        case KEY_RSA1:
+       case KEY_RSA_CERT_V00:
+       case KEY_RSA_CERT:
        case KEY_RSA:
                return a->rsa != NULL && b->rsa != NULL &&
                    BN_cmp(a->rsa->e, b->rsa->e) == 0 &&
                    BN_cmp(a->rsa->n, b->rsa->n) == 0;
+       case KEY_DSA_CERT_V00:
+       case KEY_DSA_CERT:
        case KEY_DSA:
                return a->dsa != NULL && b->dsa != NULL &&
                    BN_cmp(a->dsa->p, b->dsa->p) == 0 &&
@@ -177,16 +267,27 @@ key_equal(const Key *a, const Key *b)
        /* NOTREACHED */
 }
 
+int
+key_equal(const Key *a, const Key *b)
+{
+       if (a == NULL || b == NULL || a->type != b->type)
+               return 0;
+       if (key_is_cert(a)) {
+               if (!cert_compare(a->cert, b->cert))
+                       return 0;
+       }
+       return key_equal_public(a, b);
+}
+
 u_char*
-key_fingerprint_raw(const Key *k, enum fp_type dgst_type,
-    u_int *dgst_raw_length)
+key_fingerprint_raw(Key *k, enum fp_type dgst_type, u_int *dgst_raw_length)
 {
        const EVP_MD *md = NULL;
        EVP_MD_CTX ctx;
        u_char *blob = NULL;
        u_char *retval = NULL;
        u_int len = 0;
-       int nlen, elen;
+       int nlen, elen, otype;
 
        *dgst_raw_length = 0;
 
@@ -214,6 +315,16 @@ key_fingerprint_raw(const Key *k, enum fp_type dgst_type,
        case KEY_RSA:
                key_to_blob(k, &blob, &len);
                break;
+       case KEY_DSA_CERT_V00:
+       case KEY_RSA_CERT_V00:
+       case KEY_DSA_CERT:
+       case KEY_RSA_CERT:
+               /* We want a fingerprint of the _key_ not of the cert */
+               otype = k->type;
+               k->type = key_type_plain(k->type);
+               key_to_blob(k, &blob, &len);
+               k->type = otype;
+               break;
        case KEY_UNSPEC:
                return retval;
        default:
@@ -408,7 +519,7 @@ key_fingerprint_randomart(u_char *dgst_raw, u_int dgst_raw_len, const Key *k)
 }
 
 char *
-key_fingerprint(const Key *k, enum fp_type dgst_type, enum fp_rep dgst_rep)
+key_fingerprint(Key *k, enum fp_type dgst_type, enum fp_rep dgst_rep)
 {
        char *retval = NULL;
        u_char *dgst_raw;
@@ -522,11 +633,21 @@ key_read(Key *ret, char **cpp)
                        return -1;
                if (!read_bignum(cpp, ret->rsa->n))
                        return -1;
+               /* validate the claimed number of bits */
+               if ((u_int)BN_num_bits(ret->rsa->n) != bits) {
+                       verbose("key_read: claimed key size %d does not match "
+                          "actual %d", bits, BN_num_bits(ret->rsa->n));
+                       return -1;
+               }
                success = 1;
                break;
        case KEY_UNSPEC:
        case KEY_RSA:
        case KEY_DSA:
+       case KEY_DSA_CERT_V00:
+       case KEY_RSA_CERT_V00:
+       case KEY_DSA_CERT:
+       case KEY_RSA_CERT:
                space = strchr(cp, ' ');
                if (space == NULL) {
                        debug3("key_read: missing whitespace");
@@ -571,25 +692,36 @@ key_read(Key *ret, char **cpp)
                        return -1;
                }
 /*XXXX*/
-               if (ret->type == KEY_RSA) {
+               if (key_is_cert(ret)) {
+                       if (!key_is_cert(k)) {
+                               error("key_read: loaded key is not a cert");
+                               key_free(k);
+                               return -1;
+                       }
+                       if (ret->cert != NULL)
+                               cert_free(ret->cert);
+                       ret->cert = k->cert;
+                       k->cert = NULL;
+               }
+               if (key_type_plain(ret->type) == KEY_RSA) {
                        if (ret->rsa != NULL)
                                RSA_free(ret->rsa);
                        ret->rsa = k->rsa;
                        k->rsa = NULL;
-                       success = 1;
 #ifdef DEBUG_PK
                        RSA_print_fp(stderr, ret->rsa, 8);
 #endif
-               } else {
+               }
+               if (key_type_plain(ret->type) == KEY_DSA) {
                        if (ret->dsa != NULL)
                                DSA_free(ret->dsa);
                        ret->dsa = k->dsa;
                        k->dsa = NULL;
-                       success = 1;
 #ifdef DEBUG_PK
                        DSA_print_fp(stderr, ret->dsa, 8);
 #endif
                }
+               success = 1;
 /*XXXX*/
                key_free(k);
                if (success != 1)
@@ -616,28 +748,55 @@ key_write(const Key *key, FILE *f)
        u_char *blob;
        char *uu;
 
-       if (key->type == KEY_RSA1 && key->rsa != NULL) {
+       if (key_is_cert(key)) {
+               if (key->cert == NULL) {
+                       error("%s: no cert data", __func__);
+                       return 0;
+               }
+               if (buffer_len(&key->cert->certblob) == 0) {
+                       error("%s: no signed certificate blob", __func__);
+                       return 0;
+               }
+       }
+
+       switch (key->type) {
+       case KEY_RSA1:
+               if (key->rsa == NULL)
+                       return 0;
                /* size of modulus 'n' */
                bits = BN_num_bits(key->rsa->n);
                fprintf(f, "%u", bits);
                if (write_bignum(f, key->rsa->e) &&
-                   write_bignum(f, key->rsa->n)) {
-                       success = 1;
-               } else {
-                       error("key_write: failed for RSA key");
-               }
-       } else if ((key->type == KEY_DSA && key->dsa != NULL) ||
-           (key->type == KEY_RSA && key->rsa != NULL)) {
-               key_to_blob(key, &blob, &len);
-               uu = xmalloc(2*len);
-               n = uuencode(blob, len, uu, 2*len);
-               if (n > 0) {
-                       fprintf(f, "%s %s", key_ssh_name(key), uu);
-                       success = 1;
-               }
-               xfree(blob);
-               xfree(uu);
+                   write_bignum(f, key->rsa->n))
+                       return 1;
+               error("key_write: failed for RSA key");
+               return 0;
+       case KEY_DSA:
+       case KEY_DSA_CERT_V00:
+       case KEY_DSA_CERT:
+               if (key->dsa == NULL)
+                       return 0;
+               break;
+       case KEY_RSA:
+       case KEY_RSA_CERT_V00:
+       case KEY_RSA_CERT:
+               if (key->rsa == NULL)
+                       return 0;
+               break;
+       default:
+               return 0;
+       }
+
+       key_to_blob(key, &blob, &len);
+       uu = xmalloc(2*len);
+       n = uuencode(blob, len, uu, 2*len);
+       if (n > 0) {
+               fprintf(f, "%s %s", key_ssh_name(key), uu);
+               success = 1;
        }
+       xfree(blob);
+       xfree(uu);
+
        return success;
 }
 
@@ -651,10 +810,31 @@ key_type(const Key *k)
                return "RSA";
        case KEY_DSA:
                return "DSA";
+       case KEY_RSA_CERT_V00:
+               return "RSA-CERT-V00";
+       case KEY_DSA_CERT_V00:
+               return "DSA-CERT-V00";
+       case KEY_RSA_CERT:
+               return "RSA-CERT";
+       case KEY_DSA_CERT:
+               return "DSA-CERT";
        }
        return "unknown";
 }
 
+const char *
+key_cert_type(const Key *k)
+{
+       switch (k->cert->type) {
+       case SSH2_CERT_TYPE_USER:
+               return "user";
+       case SSH2_CERT_TYPE_HOST:
+               return "host";
+       default:
+               return "unknown";
+       }
+}
+
 const char *
 key_ssh_name(const Key *k)
 {
@@ -663,6 +843,14 @@ key_ssh_name(const Key *k)
                return "ssh-rsa";
        case KEY_DSA:
                return "ssh-dss";
+       case KEY_RSA_CERT_V00:
+               return "ssh-rsa-cert-v00@openssh.com";
+       case KEY_DSA_CERT_V00:
+               return "ssh-dss-cert-v00@openssh.com";
+       case KEY_RSA_CERT:
+               return "ssh-rsa-cert-v01@openssh.com";
+       case KEY_DSA_CERT:
+               return "ssh-dss-cert-v01@openssh.com";
        }
        return "ssh-unknown";
 }
@@ -673,8 +861,12 @@ key_size(const Key *k)
        switch (k->type) {
        case KEY_RSA1:
        case KEY_RSA:
+       case KEY_RSA_CERT_V00:
+       case KEY_RSA_CERT:
                return BN_num_bits(k->rsa->n);
        case KEY_DSA:
+       case KEY_DSA_CERT_V00:
+       case KEY_DSA_CERT:
                return BN_num_bits(k->dsa->p);
        }
        return 0;
@@ -685,7 +877,7 @@ rsa_generate_private_key(u_int bits)
 {
        RSA *private;
 
-       private = RSA_generate_key(bits, 35, NULL, NULL);
+       private = RSA_generate_key(bits, RSA_F4, NULL, NULL);
        if (private == NULL)
                fatal("rsa_generate_private_key: key generation failed.");
        return private;
@@ -717,6 +909,11 @@ key_generate(int type, u_int bits)
        case KEY_RSA1:
                k->rsa = rsa_generate_private_key(bits);
                break;
+       case KEY_RSA_CERT_V00:
+       case KEY_DSA_CERT_V00:
+       case KEY_RSA_CERT:
+       case KEY_DSA_CERT:
+               fatal("key_generate: cert keys cannot be generated directly");
        default:
                fatal("key_generate: unknown type %d", type);
        }
@@ -724,12 +921,59 @@ key_generate(int type, u_int bits)
        return k;
 }
 
+void
+key_cert_copy(const Key *from_key, struct Key *to_key)
+{
+       u_int i;
+       const struct KeyCert *from;
+       struct KeyCert *to;
+
+       if (to_key->cert != NULL) {
+               cert_free(to_key->cert);
+               to_key->cert = NULL;
+       }
+
+       if ((from = from_key->cert) == NULL)
+               return;
+
+       to = to_key->cert = cert_new();
+
+       buffer_append(&to->certblob, buffer_ptr(&from->certblob),
+           buffer_len(&from->certblob));
+
+       buffer_append(&to->critical,
+           buffer_ptr(&from->critical), buffer_len(&from->critical));
+       buffer_append(&to->extensions,
+           buffer_ptr(&from->extensions), buffer_len(&from->extensions));
+
+       to->serial = from->serial;
+       to->type = from->type;
+       to->key_id = from->key_id == NULL ? NULL : xstrdup(from->key_id);
+       to->valid_after = from->valid_after;
+       to->valid_before = from->valid_before;
+       to->signature_key = from->signature_key == NULL ?
+           NULL : key_from_private(from->signature_key);
+
+       to->nprincipals = from->nprincipals;
+       if (to->nprincipals > CERT_MAX_PRINCIPALS)
+               fatal("%s: nprincipals (%u) > CERT_MAX_PRINCIPALS (%u)",
+                   __func__, to->nprincipals, CERT_MAX_PRINCIPALS);
+       if (to->nprincipals > 0) {
+               to->principals = xcalloc(from->nprincipals,
+                   sizeof(*to->principals));
+               for (i = 0; i < to->nprincipals; i++)
+                       to->principals[i] = xstrdup(from->principals[i]);
+       }
+}
+
 Key *
 key_from_private(const Key *k)
 {
        Key *n = NULL;
        switch (k->type) {
        case KEY_DSA:
+       case KEY_DSA_CERT_V00:
+       case KEY_DSA_CERT:
                n = key_new(k->type);
                if ((BN_copy(n->dsa->p, k->dsa->p) == NULL) ||
                    (BN_copy(n->dsa->q, k->dsa->q) == NULL) ||
@@ -739,6 +983,8 @@ key_from_private(const Key *k)
                break;
        case KEY_RSA:
        case KEY_RSA1:
+       case KEY_RSA_CERT_V00:
+       case KEY_RSA_CERT:
                n = key_new(k->type);
                if ((BN_copy(n->rsa->n, k->rsa->n) == NULL) ||
                    (BN_copy(n->rsa->e, k->rsa->e) == NULL))
@@ -748,6 +994,8 @@ key_from_private(const Key *k)
                fatal("key_from_private: unknown type %d", k->type);
                break;
        }
+       if (key_is_cert(k))
+               key_cert_copy(k, n);
        return n;
 }
 
@@ -764,6 +1012,14 @@ key_type_from_name(char *name)
                return KEY_RSA;
        } else if (strcmp(name, "ssh-dss") == 0) {
                return KEY_DSA;
+       } else if (strcmp(name, "ssh-rsa-cert-v00@openssh.com") == 0) {
+               return KEY_RSA_CERT_V00;
+       } else if (strcmp(name, "ssh-dss-cert-v00@openssh.com") == 0) {
+               return KEY_DSA_CERT_V00;
+       } else if (strcmp(name, "ssh-rsa-cert-v01@openssh.com") == 0) {
+               return KEY_RSA_CERT;
+       } else if (strcmp(name, "ssh-dss-cert-v01@openssh.com") == 0) {
+               return KEY_DSA_CERT;
        }
        debug2("key_type_from_name: unknown key type '%s'", name);
        return KEY_UNSPEC;
@@ -791,6 +1047,146 @@ key_names_valid2(const char *names)
        return 1;
 }
 
+static int
+cert_parse(Buffer *b, Key *key, const u_char *blob, u_int blen)
+{
+       u_char *principals, *critical, *exts, *sig_key, *sig;
+       u_int signed_len, plen, clen, sklen, slen, kidlen, elen;
+       Buffer tmp;
+       char *principal;
+       int ret = -1;
+       int v00 = key->type == KEY_DSA_CERT_V00 ||
+           key->type == KEY_RSA_CERT_V00;
+
+       buffer_init(&tmp);
+
+       /* Copy the entire key blob for verification and later serialisation */
+       buffer_append(&key->cert->certblob, blob, blen);
+
+       elen = 0; /* Not touched for v00 certs */
+       principals = exts = critical = sig_key = sig = NULL;
+       if ((!v00 && buffer_get_int64_ret(&key->cert->serial, b) != 0) ||
+           buffer_get_int_ret(&key->cert->type, b) != 0 ||
+           (key->cert->key_id = buffer_get_string_ret(b, &kidlen)) == NULL ||
+           (principals = buffer_get_string_ret(b, &plen)) == NULL ||
+           buffer_get_int64_ret(&key->cert->valid_after, b) != 0 ||
+           buffer_get_int64_ret(&key->cert->valid_before, b) != 0 ||
+           (critical = buffer_get_string_ret(b, &clen)) == NULL ||
+           (!v00 && (exts = buffer_get_string_ret(b, &elen)) == NULL) ||
+           (v00 && buffer_get_string_ptr_ret(b, NULL) == NULL) || /* nonce */
+           buffer_get_string_ptr_ret(b, NULL) == NULL || /* reserved */
+           (sig_key = buffer_get_string_ret(b, &sklen)) == NULL) {
+               error("%s: parse error", __func__);
+               goto out;
+       }
+
+       if (kidlen != strlen(key->cert->key_id)) {
+               error("%s: key ID contains \\0 character", __func__);
+               goto out;
+       }
+
+       /* Signature is left in the buffer so we can calculate this length */
+       signed_len = buffer_len(&key->cert->certblob) - buffer_len(b);
+
+       if ((sig = buffer_get_string_ret(b, &slen)) == NULL) {
+               error("%s: parse error", __func__);
+               goto out;
+       }
+
+       if (key->cert->type != SSH2_CERT_TYPE_USER &&
+           key->cert->type != SSH2_CERT_TYPE_HOST) {
+               error("Unknown certificate type %u", key->cert->type);
+               goto out;
+       }
+
+       buffer_append(&tmp, principals, plen);
+       while (buffer_len(&tmp) > 0) {
+               if (key->cert->nprincipals >= CERT_MAX_PRINCIPALS) {
+                       error("%s: Too many principals", __func__);
+                       goto out;
+               }
+               if ((principal = buffer_get_string_ret(&tmp, &plen)) == NULL) {
+                       error("%s: Principals data invalid", __func__);
+                       goto out;
+               }
+               if (strlen(principal) != plen) {
+                       error("%s: Principal contains \\0 character",
+                           __func__);
+                       goto out;
+               }
+               key->cert->principals = xrealloc(key->cert->principals,
+                   key->cert->nprincipals + 1, sizeof(*key->cert->principals));
+               key->cert->principals[key->cert->nprincipals++] = principal;
+       }
+
+       buffer_clear(&tmp);
+
+       buffer_append(&key->cert->critical, critical, clen);
+       buffer_append(&tmp, critical, clen);
+       /* validate structure */
+       while (buffer_len(&tmp) != 0) {
+               if (buffer_get_string_ptr_ret(&tmp, NULL) == NULL ||
+                   buffer_get_string_ptr_ret(&tmp, NULL) == NULL) {
+                       error("%s: critical option data invalid", __func__);
+                       goto out;
+               }
+       }
+       buffer_clear(&tmp);
+
+       buffer_append(&key->cert->extensions, exts, elen);
+       buffer_append(&tmp, exts, elen);
+       /* validate structure */
+       while (buffer_len(&tmp) != 0) {
+               if (buffer_get_string_ptr_ret(&tmp, NULL) == NULL ||
+                   buffer_get_string_ptr_ret(&tmp, NULL) == NULL) {
+                       error("%s: extension data invalid", __func__);
+                       goto out;
+               }
+       }
+       buffer_clear(&tmp);
+
+       if ((key->cert->signature_key = key_from_blob(sig_key,
+           sklen)) == NULL) {
+               error("%s: Signature key invalid", __func__);
+               goto out;
+       }
+       if (key->cert->signature_key->type != KEY_RSA &&
+           key->cert->signature_key->type != KEY_DSA) {
+               error("%s: Invalid signature key type %s (%d)", __func__,
+                   key_type(key->cert->signature_key),
+                   key->cert->signature_key->type);
+               goto out;
+       }
+
+       switch (key_verify(key->cert->signature_key, sig, slen, 
+           buffer_ptr(&key->cert->certblob), signed_len)) {
+       case 1:
+               ret = 0;
+               break; /* Good signature */
+       case 0:
+               error("%s: Invalid signature on certificate", __func__);
+               goto out;
+       case -1:
+               error("%s: Certificate signature verification failed",
+                   __func__);
+               goto out;
+       }
+
+ out:
+       buffer_free(&tmp);
+       if (principals != NULL)
+               xfree(principals);
+       if (critical != NULL)
+               xfree(critical);
+       if (exts != NULL)
+               xfree(exts);
+       if (sig_key != NULL)
+               xfree(sig_key);
+       if (sig != NULL)
+               xfree(sig);
+       return ret;
+}
+
 Key *
 key_from_blob(const u_char *blob, u_int blen)
 {
@@ -812,11 +1208,16 @@ key_from_blob(const u_char *blob, u_int blen)
        type = key_type_from_name(ktype);
 
        switch (type) {
+       case KEY_RSA_CERT:
+               (void)buffer_get_string_ptr_ret(&b, NULL); /* Skip nonce */
+               /* FALLTHROUGH */
        case KEY_RSA:
+       case KEY_RSA_CERT_V00:
                key = key_new(type);
                if (buffer_get_bignum2_ret(&b, key->rsa->e) == -1 ||
                    buffer_get_bignum2_ret(&b, key->rsa->n) == -1) {
                        error("key_from_blob: can't read rsa key");
+ badkey:
                        key_free(key);
                        key = NULL;
                        goto out;
@@ -825,16 +1226,18 @@ key_from_blob(const u_char *blob, u_int blen)
                RSA_print_fp(stderr, key->rsa, 8);
 #endif
                break;
+       case KEY_DSA_CERT:
+               (void)buffer_get_string_ptr_ret(&b, NULL); /* Skip nonce */
+               /* FALLTHROUGH */
        case KEY_DSA:
+       case KEY_DSA_CERT_V00:
                key = key_new(type);
                if (buffer_get_bignum2_ret(&b, key->dsa->p) == -1 ||
                    buffer_get_bignum2_ret(&b, key->dsa->q) == -1 ||
                    buffer_get_bignum2_ret(&b, key->dsa->g) == -1 ||
                    buffer_get_bignum2_ret(&b, key->dsa->pub_key) == -1) {
                        error("key_from_blob: can't read dsa key");
-                       key_free(key);
-                       key = NULL;
-                       goto out;
+                       goto badkey;
                }
 #ifdef DEBUG_PK
                DSA_print_fp(stderr, key->dsa, 8);
@@ -847,6 +1250,10 @@ key_from_blob(const u_char *blob, u_int blen)
                error("key_from_blob: cannot handle type %s", ktype);
                goto out;
        }
+       if (key_is_cert(key) && cert_parse(&b, key, blob, blen) == -1) {
+               error("key_from_blob: can't parse cert data");
+               goto badkey;
+       }
        rlen = buffer_len(&b);
        if (key != NULL && rlen != 0)
                error("key_from_blob: remaining bytes in key blob %d", rlen);
@@ -869,6 +1276,14 @@ key_to_blob(const Key *key, u_char **blobp, u_int *lenp)
        }
        buffer_init(&b);
        switch (key->type) {
+       case KEY_DSA_CERT_V00:
+       case KEY_RSA_CERT_V00:
+       case KEY_DSA_CERT:
+       case KEY_RSA_CERT:
+               /* Use the existing blob */
+               buffer_append(&b, buffer_ptr(&key->cert->certblob),
+                   buffer_len(&key->cert->certblob));
+               break;
        case KEY_DSA:
                buffer_put_cstring(&b, key_ssh_name(key));
                buffer_put_bignum2(&b, key->dsa->p);
@@ -905,8 +1320,12 @@ key_sign(
     const u_char *data, u_int datalen)
 {
        switch (key->type) {
+       case KEY_DSA_CERT_V00:
+       case KEY_DSA_CERT:
        case KEY_DSA:
                return ssh_dss_sign(key, sigp, lenp, data, datalen);
+       case KEY_RSA_CERT_V00:
+       case KEY_RSA_CERT:
        case KEY_RSA:
                return ssh_rsa_sign(key, sigp, lenp, data, datalen);
        default:
@@ -929,8 +1348,12 @@ key_verify(
                return -1;
 
        switch (key->type) {
+       case KEY_DSA_CERT_V00:
+       case KEY_DSA_CERT:
        case KEY_DSA:
                return ssh_dss_verify(key, signature, signaturelen, data, datalen);
+       case KEY_RSA_CERT_V00:
+       case KEY_RSA_CERT:
        case KEY_RSA:
                return ssh_rsa_verify(key, signature, signaturelen, data, datalen);
        default:
@@ -952,6 +1375,10 @@ key_demote(const Key *k)
        pk->rsa = NULL;
 
        switch (k->type) {
+       case KEY_RSA_CERT_V00:
+       case KEY_RSA_CERT:
+               key_cert_copy(k, pk);
+               /* FALLTHROUGH */
        case KEY_RSA1:
        case KEY_RSA:
                if ((pk->rsa = RSA_new()) == NULL)
@@ -961,6 +1388,10 @@ key_demote(const Key *k)
                if ((pk->rsa->n = BN_dup(k->rsa->n)) == NULL)
                        fatal("key_demote: BN_dup failed");
                break;
+       case KEY_DSA_CERT_V00:
+       case KEY_DSA_CERT:
+               key_cert_copy(k, pk);
+               /* FALLTHROUGH */
        case KEY_DSA:
                if ((pk->dsa = DSA_new()) == NULL)
                        fatal("key_demote: DSA_new failed");
@@ -980,3 +1411,244 @@ key_demote(const Key *k)
 
        return (pk);
 }
+
+int
+key_is_cert(const Key *k)
+{
+       if (k == NULL)
+               return 0;
+       switch (k->type) {
+       case KEY_RSA_CERT_V00:
+       case KEY_DSA_CERT_V00:
+       case KEY_RSA_CERT:
+       case KEY_DSA_CERT:
+               return 1;
+       default:
+               return 0;
+       }
+}
+
+/* Return the cert-less equivalent to a certified key type */
+int
+key_type_plain(int type)
+{
+       switch (type) {
+       case KEY_RSA_CERT_V00:
+       case KEY_RSA_CERT:
+               return KEY_RSA;
+       case KEY_DSA_CERT_V00:
+       case KEY_DSA_CERT:
+               return KEY_DSA;
+       default:
+               return type;
+       }
+}
+
+/* Convert a KEY_RSA or KEY_DSA to their _CERT equivalent */
+int
+key_to_certified(Key *k, int legacy)
+{
+       switch (k->type) {
+       case KEY_RSA:
+               k->cert = cert_new();
+               k->type = legacy ? KEY_RSA_CERT_V00 : KEY_RSA_CERT;
+               return 0;
+       case KEY_DSA:
+               k->cert = cert_new();
+               k->type = legacy ? KEY_DSA_CERT_V00 : KEY_DSA_CERT;
+               return 0;
+       default:
+               error("%s: key has incorrect type %s", __func__, key_type(k));
+               return -1;
+       }
+}
+
+/* Convert a KEY_RSA_CERT or KEY_DSA_CERT to their raw key equivalent */
+int
+key_drop_cert(Key *k)
+{
+       switch (k->type) {
+       case KEY_RSA_CERT_V00:
+       case KEY_RSA_CERT:
+               cert_free(k->cert);
+               k->type = KEY_RSA;
+               return 0;
+       case KEY_DSA_CERT_V00:
+       case KEY_DSA_CERT:
+               cert_free(k->cert);
+               k->type = KEY_DSA;
+               return 0;
+       default:
+               error("%s: key has incorrect type %s", __func__, key_type(k));
+               return -1;
+       }
+}
+
+/* Sign a KEY_RSA_CERT or KEY_DSA_CERT, (re-)generating the signed certblob */
+int
+key_certify(Key *k, Key *ca)
+{
+       Buffer principals;
+       u_char *ca_blob, *sig_blob, nonce[32];
+       u_int i, ca_len, sig_len;
+
+       if (k->cert == NULL) {
+               error("%s: key lacks cert info", __func__);
+               return -1;
+       }
+
+       if (!key_is_cert(k)) {
+               error("%s: certificate has unknown type %d", __func__,
+                   k->cert->type);
+               return -1;
+       }
+
+       if (ca->type != KEY_RSA && ca->type != KEY_DSA) {
+               error("%s: CA key has unsupported type %s", __func__,
+                   key_type(ca));
+               return -1;
+       }
+
+       key_to_blob(ca, &ca_blob, &ca_len);
+
+       buffer_clear(&k->cert->certblob);
+       buffer_put_cstring(&k->cert->certblob, key_ssh_name(k));
+
+       /* -v01 certs put nonce first */
+       if (k->type == KEY_DSA_CERT || k->type == KEY_RSA_CERT) {
+               arc4random_buf(&nonce, sizeof(nonce));
+               buffer_put_string(&k->cert->certblob, nonce, sizeof(nonce));
+       }
+
+       switch (k->type) {
+       case KEY_DSA_CERT_V00:
+       case KEY_DSA_CERT:
+               buffer_put_bignum2(&k->cert->certblob, k->dsa->p);
+               buffer_put_bignum2(&k->cert->certblob, k->dsa->q);
+               buffer_put_bignum2(&k->cert->certblob, k->dsa->g);
+               buffer_put_bignum2(&k->cert->certblob, k->dsa->pub_key);
+               break;
+       case KEY_RSA_CERT_V00:
+       case KEY_RSA_CERT:
+               buffer_put_bignum2(&k->cert->certblob, k->rsa->e);
+               buffer_put_bignum2(&k->cert->certblob, k->rsa->n);
+               break;
+       default:
+               error("%s: key has incorrect type %s", __func__, key_type(k));
+               buffer_clear(&k->cert->certblob);
+               xfree(ca_blob);
+               return -1;
+       }
+
+       /* -v01 certs have a serial number next */
+       if (k->type == KEY_DSA_CERT || k->type == KEY_RSA_CERT)
+               buffer_put_int64(&k->cert->certblob, k->cert->serial);
+
+       buffer_put_int(&k->cert->certblob, k->cert->type);
+       buffer_put_cstring(&k->cert->certblob, k->cert->key_id);
+
+       buffer_init(&principals);
+       for (i = 0; i < k->cert->nprincipals; i++)
+               buffer_put_cstring(&principals, k->cert->principals[i]);
+       buffer_put_string(&k->cert->certblob, buffer_ptr(&principals),
+           buffer_len(&principals));
+       buffer_free(&principals);
+
+       buffer_put_int64(&k->cert->certblob, k->cert->valid_after);
+       buffer_put_int64(&k->cert->certblob, k->cert->valid_before);
+       buffer_put_string(&k->cert->certblob,
+           buffer_ptr(&k->cert->critical), buffer_len(&k->cert->critical));
+
+       /* -v01 certs have non-critical options here */
+       if (k->type == KEY_DSA_CERT || k->type == KEY_RSA_CERT) {
+               buffer_put_string(&k->cert->certblob,
+                   buffer_ptr(&k->cert->extensions),
+                   buffer_len(&k->cert->extensions));
+       }
+
+       /* -v00 certs put the nonce at the end */
+       if (k->type == KEY_DSA_CERT_V00 || k->type == KEY_RSA_CERT_V00)
+               buffer_put_string(&k->cert->certblob, nonce, sizeof(nonce));
+
+       buffer_put_string(&k->cert->certblob, NULL, 0); /* reserved */
+       buffer_put_string(&k->cert->certblob, ca_blob, ca_len);
+       xfree(ca_blob);
+
+       /* Sign the whole mess */
+       if (key_sign(ca, &sig_blob, &sig_len, buffer_ptr(&k->cert->certblob),
+           buffer_len(&k->cert->certblob)) != 0) {
+               error("%s: signature operation failed", __func__);
+               buffer_clear(&k->cert->certblob);
+               return -1;
+       }
+       /* Append signature and we are done */
+       buffer_put_string(&k->cert->certblob, sig_blob, sig_len);
+       xfree(sig_blob);
+
+       return 0;
+}
+
+int
+key_cert_check_authority(const Key *k, int want_host, int require_principal,
+    const char *name, const char **reason)
+{
+       u_int i, principal_matches;
+       time_t now = time(NULL);
+
+       if (want_host) {
+               if (k->cert->type != SSH2_CERT_TYPE_HOST) {
+                       *reason = "Certificate invalid: not a host certificate";
+                       return -1;
+               }
+       } else {
+               if (k->cert->type != SSH2_CERT_TYPE_USER) {
+                       *reason = "Certificate invalid: not a user certificate";
+                       return -1;
+               }
+       }
+       if (now < 0) {
+               error("%s: system clock lies before epoch", __func__);
+               *reason = "Certificate invalid: not yet valid";
+               return -1;
+       }
+       if ((u_int64_t)now < k->cert->valid_after) {
+               *reason = "Certificate invalid: not yet valid";
+               return -1;
+       }
+       if ((u_int64_t)now >= k->cert->valid_before) {
+               *reason = "Certificate invalid: expired";
+               return -1;
+       }
+       if (k->cert->nprincipals == 0) {
+               if (require_principal) {
+                       *reason = "Certificate lacks principal list";
+                       return -1;
+               }
+       } else if (name != NULL) {
+               principal_matches = 0;
+               for (i = 0; i < k->cert->nprincipals; i++) {
+                       if (strcmp(name, k->cert->principals[i]) == 0) {
+                               principal_matches = 1;
+                               break;
+                       }
+               }
+               if (!principal_matches) {
+                       *reason = "Certificate invalid: name is not a listed "
+                           "principal";
+                       return -1;
+               }
+       }
+       return 0;
+}
+
+int
+key_cert_is_legacy(Key *k)
+{
+       switch (k->type) {
+       case KEY_DSA_CERT_V00:
+       case KEY_RSA_CERT_V00:
+               return 1;
+       default:
+               return 0;
+       }
+}
index 14aac79..11d30ea 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: key.h,v 1.27 2008/06/11 21:01:35 grunk Exp $ */
+/* $OpenBSD: key.h,v 1.30 2010/04/16 01:47:26 djm Exp $ */
 
 /*
  * Copyright (c) 2000, 2001 Markus Friedl.  All rights reserved.
@@ -26,6 +26,7 @@
 #ifndef KEY_H
 #define KEY_H
 
+#include "buffer.h"
 #include <openssl/rsa.h>
 #include <openssl/dsa.h>
 
@@ -34,6 +35,10 @@ enum types {
        KEY_RSA1,
        KEY_RSA,
        KEY_DSA,
+       KEY_RSA_CERT,
+       KEY_DSA_CERT,
+       KEY_RSA_CERT_V00,
+       KEY_DSA_CERT_V00,
        KEY_UNSPEC
 };
 enum fp_type {
@@ -49,21 +54,39 @@ enum fp_rep {
 /* key is stored in external hardware */
 #define KEY_FLAG_EXT           0x0001
 
+#define CERT_MAX_PRINCIPALS    256
+struct KeyCert {
+       Buffer           certblob; /* Kept around for use on wire */
+       u_int            type; /* SSH2_CERT_TYPE_USER or SSH2_CERT_TYPE_HOST */
+       u_int64_t        serial;
+       char            *key_id;
+       u_int            nprincipals;
+       char            **principals;
+       u_int64_t        valid_after, valid_before;
+       Buffer           critical;
+       Buffer           extensions;
+       Key             *signature_key;
+};
+
 struct Key {
        int      type;
        int      flags;
        RSA     *rsa;
        DSA     *dsa;
+       struct KeyCert *cert;
 };
 
 Key            *key_new(int);
+void            key_add_private(Key *);
 Key            *key_new_private(int);
 void            key_free(Key *);
 Key            *key_demote(const Key *);
+int             key_equal_public(const Key *, const Key *);
 int             key_equal(const Key *, const Key *);
-char           *key_fingerprint(const Key *, enum fp_type, enum fp_rep);
-u_char         *key_fingerprint_raw(const Key *, enum fp_type, u_int *);
+char           *key_fingerprint(Key *, enum fp_type, enum fp_rep);
+u_char         *key_fingerprint_raw(Key *, enum fp_type, u_int *);
 const char     *key_type(const Key *);
+const char     *key_cert_type(const Key *);
 int             key_write(const Key *, FILE *);
 int             key_read(Key *, char **);
 u_int           key_size(const Key *);
@@ -71,6 +94,15 @@ u_int                 key_size(const Key *);
 Key    *key_generate(int, u_int);
 Key    *key_from_private(const Key *);
 int     key_type_from_name(char *);
+int     key_is_cert(const Key *);
+int     key_type_plain(int);
+int     key_to_certified(Key *, int);
+int     key_drop_cert(Key *);
+int     key_certify(Key *, Key *);
+void    key_cert_copy(const Key *, struct Key *);
+int     key_cert_check_authority(const Key *, int, int, const char *,
+           const char **);
+int     key_cert_is_legacy(Key *);
 
 Key            *key_from_blob(const u_char *, u_int);
 int             key_to_blob(const Key *, u_char **, u_int *);
index f4af067..6f655cb 100644 (file)
@@ -207,6 +207,7 @@ int syslogin_write_entry(struct logininfo *li);
 
 int getlast_entry(struct logininfo *li);
 int lastlog_get_entry(struct logininfo *li);
+int utmpx_get_entry(struct logininfo *li);
 int wtmp_get_entry(struct logininfo *li);
 int wtmpx_get_entry(struct logininfo *li);
 
@@ -508,6 +509,10 @@ getlast_entry(struct logininfo *li)
 #ifdef USE_LASTLOG
        return(lastlog_get_entry(li));
 #else /* !USE_LASTLOG */
+#if defined(USE_UTMPX) && defined(HAVE_SETUTXDB) && \
+    defined(UTXDB_LASTLOGIN) && defined(HAVE_GETUTXUSER)
+       return (utmpx_get_entry(li));
+#endif
 
 #if defined(DISABLE_LASTLOG)
        /* On some systems we shouldn't even try to obtain last login
@@ -758,8 +763,8 @@ construct_utmpx(struct logininfo *li, struct utmpx *utx)
        utx->ut_pid = li->pid;
 
        /* strncpy(): Don't necessarily want null termination */
-       strncpy(utx->ut_name, li->username,
-           MIN_SIZEOF(utx->ut_name, li->username));
+       strncpy(utx->ut_user, li->username,
+           MIN_SIZEOF(utx->ut_user, li->username));
 
        if (li->type == LTYPE_LOGOUT)
                return;
@@ -1316,8 +1321,8 @@ wtmpx_write_entry(struct logininfo *li)
 static int
 wtmpx_islogin(struct logininfo *li, struct utmpx *utx)
 {
-       if (strncmp(li->username, utx->ut_name,
-           MIN_SIZEOF(li->username, utx->ut_name)) == 0 ) {
+       if (strncmp(li->username, utx->ut_user,
+           MIN_SIZEOF(li->username, utx->ut_user)) == 0 ) {
 # ifdef HAVE_TYPE_IN_UTMPX
                if (utx->ut_type == USER_PROCESS)
                        return (1);
@@ -1608,6 +1613,32 @@ lastlog_get_entry(struct logininfo *li)
 #endif /* HAVE_GETLASTLOGXBYNAME */
 #endif /* USE_LASTLOG */
 
+#if defined(USE_UTMPX) && defined(HAVE_SETUTXDB) && \
+    defined(UTXDB_LASTLOGIN) && defined(HAVE_GETUTXUSER)
+int
+utmpx_get_entry(struct logininfo *li)
+{
+       struct utmpx *utx;
+
+       if (setutxdb(UTXDB_LASTLOGIN, NULL) != 0)
+               return (0);
+       utx = getutxuser(li->username);
+       if (utx == NULL) {
+               endutxent();
+               return (0);
+       }
+
+       line_fullname(li->line, utx->ut_line,
+           MIN_SIZEOF(li->line, utx->ut_line));
+       strlcpy(li->hostname, utx->ut_host,
+           MIN_SIZEOF(li->hostname, utx->ut_host));
+       li->tv_sec = utx->ut_tv.tv_sec;
+       li->tv_usec = utx->ut_tv.tv_usec;
+       endutxent();
+       return (1);
+}
+#endif /* USE_UTMPX && HAVE_SETUTXDB && UTXDB_LASTLOGIN && HAVE_GETUTXUSER */
+
 #ifdef USE_BTMP
   /*
    * Logs failed login attempts in _PATH_BTMP if that exists.
index 859e1a6..84b4865 100644 (file)
@@ -56,7 +56,7 @@ union login_netinfo {
 /* string lengths - set very long */
 #define LINFO_PROGSIZE 64
 #define LINFO_LINESIZE 64
-#define LINFO_NAMESIZE 128
+#define LINFO_NAMESIZE 512
 #define LINFO_HOSTSIZE 256
 
 struct logininfo {
index 18f6830..3d7f70f 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: match.h,v 1.14 2008/06/10 03:57:27 djm Exp $ */
+/* $OpenBSD: match.h,v 1.15 2010/02/26 20:29:54 djm Exp $ */
 
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -23,5 +23,5 @@ char  *match_list(const char *, const char *, u_int *);
 
 /* addrmatch.c */
 int     addr_match_list(const char *, const char *);
-
+int     addr_match_cidr_list(const char *, const char *);
 #endif
index 143dbf0..a82e793 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: misc.c,v 1.71 2009/02/21 19:32:04 tobias Exp $ */
+/* $OpenBSD: misc.c,v 1.80 2010/07/21 02:10:58 djm Exp $ */
 /*
  * Copyright (c) 2000 Markus Friedl.  All rights reserved.
  * Copyright (c) 2005,2006 Damien Miller.  All rights reserved.
@@ -178,6 +178,7 @@ strdelim(char **s)
                        return (NULL);          /* no matching quote */
                } else {
                        *s[0] = '\0';
+                       *s += strspn(*s + 1, WHITESPACE) + 1;
                        return (old);
                }
        }
@@ -425,7 +426,7 @@ colon(char *cp)
        int flag = 0;
 
        if (*cp == ':')         /* Leading colon is part of file name. */
-               return (0);
+               return NULL;
        if (*cp == '[')
                flag = 1;
 
@@ -437,9 +438,9 @@ colon(char *cp)
                if (*cp == ':' && !flag)
                        return (cp);
                if (*cp == '/')
-                       return (0);
+                       return NULL;
        }
-       return (0);
+       return NULL;
 }
 
 /* function to assist building execv() arguments */
@@ -560,11 +561,11 @@ 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];
-       u_int num_keys, i, j;
        char buf[4096];
        va_list ap;
 
@@ -576,13 +577,12 @@ percent_expand(const char *string, ...)
                        break;
                keys[num_keys].repl = va_arg(ap, char *);
                if (keys[num_keys].repl == NULL)
-                       fatal("percent_expand: NULL replacement");
+                       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);
 
-       if (num_keys >= EXPAND_MAX_KEYS)
-               fatal("percent_expand: too many keys");
-
        /* Expand string */
        *buf = '\0';
        for (i = 0; *string != '\0'; string++) {
@@ -590,23 +590,24 @@ percent_expand(const char *string, ...)
  append:
                        buf[i++] = *string;
                        if (i >= sizeof(buf))
-                               fatal("percent_expand: string too long");
+                               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("percent_expand: string too long");
+                                       fatal("%s: string too long", __func__);
                                break;
                        }
                }
                if (j >= num_keys)
-                       fatal("percent_expand: unknown key %%%c", *string);
+                       fatal("%s: unknown key %%%c", __func__, *string);
        }
        return (xstrdup(buf));
 #undef EXPAND_MAX_KEYS
@@ -849,3 +850,24 @@ ms_to_timeval(struct timeval *tv, int ms)
        tv->tv_usec = (ms % 1000) * 1000;
 }
 
+int
+timingsafe_bcmp(const void *b1, const void *b2, size_t n)
+{
+       const unsigned char *p1 = b1, *p2 = b2;
+       int ret = 0;
+
+       for (; n > 0; n--)
+               ret |= *p1++ ^ *p2++;
+       return (ret != 0);
+}
+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
+}
index 5da170d..bb799f6 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: misc.h,v 1.38 2008/06/12 20:38:28 dtucker Exp $ */
+/* $OpenBSD: misc.h,v 1.43 2010/07/13 23:13:16 djm Exp $ */
 
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -35,6 +35,8 @@ 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);
+int     timingsafe_bcmp(const void *, const void *, size_t);
 
 struct passwd *pwcopy(struct passwd *);
 const char *ssh_gai_strerror(int);
index ace25c4..9eb4e35 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: monitor.c,v 1.104 2009/06/12 20:43:22 andreas Exp $ */
+/* $OpenBSD: monitor.c,v 1.108 2010/07/13 23:13:16 djm Exp $ */
 /*
  * Copyright 2002 Niels Provos <provos@citi.umich.edu>
  * Copyright 2002 Markus Friedl <markus@openbsd.org>
@@ -518,7 +518,7 @@ monitor_allowed_key(u_char *blob, u_int bloblen)
 {
        /* make sure key is allowed */
        if (key_blob == NULL || key_bloblen != bloblen ||
-           memcmp(key_blob, blob, key_bloblen))
+           timingsafe_bcmp(key_blob, blob, key_bloblen))
                return (0);
        return (1);
 }
@@ -922,8 +922,8 @@ mm_answer_pam_init_ctx(int sock, Buffer *m)
 int
 mm_answer_pam_query(int sock, Buffer *m)
 {
-       char *name, *info, **prompts;
-       u_int i, num, *echo_on;
+       char *name = NULL, *info = NULL, **prompts = NULL;
+       u_int i, num = 0, *echo_on = 0;
        int ret;
 
        debug3("%s", __func__);
@@ -997,17 +997,6 @@ mm_answer_pam_free_ctx(int sock, Buffer *m)
 }
 #endif
 
-static void
-mm_append_debug(Buffer *m)
-{
-       if (auth_debug_init && buffer_len(&auth_debug)) {
-               debug3("%s: Appending debug messages for child", __func__);
-               buffer_append(m, buffer_ptr(&auth_debug),
-                   buffer_len(&auth_debug));
-               buffer_clear(&auth_debug);
-       }
-}
-
 int
 mm_answer_keyallowed(int sock, Buffer *m)
 {
@@ -1090,8 +1079,6 @@ mm_answer_keyallowed(int sock, Buffer *m)
        buffer_put_int(m, allowed);
        buffer_put_int(m, forced_command != NULL);
 
-       mm_append_debug(m);
-
        mm_request_send(sock, MONITOR_ANS_KEYALLOWED, m);
 
        if (type == MM_RSAHOSTKEY)
@@ -1116,14 +1103,14 @@ monitor_valid_userblob(u_char *data, u_int datalen)
                len = buffer_len(&b);
                if ((session_id2 == NULL) ||
                    (len < session_id2_len) ||
-                   (memcmp(p, session_id2, session_id2_len) != 0))
+                   (timingsafe_bcmp(p, session_id2, session_id2_len) != 0))
                        fail++;
                buffer_consume(&b, session_id2_len);
        } else {
                p = buffer_get_string(&b, &len);
                if ((session_id2 == NULL) ||
                    (len != session_id2_len) ||
-                   (memcmp(p, session_id2, session_id2_len) != 0))
+                   (timingsafe_bcmp(p, session_id2, session_id2_len) != 0))
                        fail++;
                xfree(p);
        }
@@ -1171,7 +1158,7 @@ monitor_valid_hostbasedblob(u_char *data, u_int datalen, char *cuser,
        p = buffer_get_string(&b, &len);
        if ((session_id2 == NULL) ||
            (len != session_id2_len) ||
-           (memcmp(p, session_id2, session_id2_len) != 0))
+           (timingsafe_bcmp(p, session_id2, session_id2_len) != 0))
                fail++;
        xfree(p);
 
@@ -1475,8 +1462,6 @@ mm_answer_rsa_keyallowed(int sock, Buffer *m)
        if (key != NULL)
                key_free(key);
 
-       mm_append_debug(m);
-
        mm_request_send(sock, MONITOR_ANS_RSAKEYALLOWED, m);
 
        monitor_permit(mon_dispatch, MONITOR_REQ_RSACHALLENGE, allowed);
@@ -1697,9 +1682,9 @@ mm_get_kex(Buffer *m)
 
        kex = xcalloc(1, sizeof(*kex));
        kex->session_id = buffer_get_string(m, &kex->session_id_len);
-       if ((session_id2 == NULL) ||
-           (kex->session_id_len != session_id2_len) ||
-           (memcmp(kex->session_id, session_id2, session_id2_len) != 0))
+       if (session_id2 == NULL ||
+           kex->session_id_len != session_id2_len ||
+           timingsafe_bcmp(kex->session_id, session_id2, session_id2_len) != 0)
                fatal("mm_get_get: internal error: bad session id");
        kex->we_need = buffer_get_int(m);
        kex->kex[KEX_DH_GRP1_SHA1] = kexdh_server;
@@ -1721,7 +1706,8 @@ mm_get_kex(Buffer *m)
        kex->flags = buffer_get_int(m);
        kex->client_version_string = buffer_get_string(m, NULL);
        kex->server_version_string = buffer_get_string(m, NULL);
-       kex->load_host_key=&get_hostkey_by_type;
+       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;
 
        return (kex);
index 4b9a066..7eb6f5c 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: monitor_fdpass.c,v 1.18 2008/11/30 11:59:26 dtucker Exp $ */
+/* $OpenBSD: monitor_fdpass.c,v 1.19 2010/01/12 00:58:25 djm Exp $ */
 /*
  * Copyright 2001 Niels Provos <provos@citi.umich.edu>
  * All rights reserved.
@@ -34,6 +34,9 @@
 #endif
 
 #include <errno.h>
+#ifdef HAVE_POLL_H
+#include <poll.h>
+#endif
 #include <string.h>
 #include <stdarg.h>
 
@@ -55,6 +58,7 @@ mm_send_fd(int sock, int fd)
        struct iovec vec;
        char ch = '\0';
        ssize_t n;
+       struct pollfd pfd;
 
        memset(&msg, 0, sizeof(msg));
 #ifdef HAVE_ACCRIGHTS_IN_MSGHDR
@@ -75,9 +79,13 @@ mm_send_fd(int sock, int fd)
        msg.msg_iov = &vec;
        msg.msg_iovlen = 1;
 
-       while ((n = sendmsg(sock, &msg, 0)) == -1 && (errno == EAGAIN ||
-           errno == EINTR))
+       pfd.fd = sock;
+       pfd.events = POLLOUT;
+       while ((n = sendmsg(sock, &msg, 0)) == -1 &&
+           (errno == EAGAIN || errno == EINTR)) {
                debug3("%s: sendmsg(%d): %s", __func__, fd, strerror(errno));
+               (void)poll(&pfd, 1, -1);
+       }
        if (n == -1) {
                error("%s: sendmsg(%d): %s", __func__, fd,
                    strerror(errno));
@@ -112,6 +120,7 @@ mm_receive_fd(int sock)
        ssize_t n;
        char ch;
        int fd;
+       struct pollfd pfd;
 
        memset(&msg, 0, sizeof(msg));
        vec.iov_base = &ch;
@@ -126,9 +135,13 @@ mm_receive_fd(int sock)
        msg.msg_controllen = sizeof(cmsgbuf.buf);
 #endif
 
-       while ((n = recvmsg(sock, &msg, 0)) == -1 && (errno == EAGAIN ||
-           errno == EINTR))
+       pfd.fd = sock;
+       pfd.events = POLLIN;
+       while ((n = recvmsg(sock, &msg, 0)) == -1 &&
+           (errno == EAGAIN || errno == EINTR)) {
                debug3("%s: recvmsg: %s", __func__, strerror(errno));
+               (void)poll(&pfd, 1, -1);
+       }
        if (n == -1) {
                error("%s: recvmsg: %s", __func__, strerror(errno));
                return -1;
index b8e8710..faeb02c 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: monitor_wrap.c,v 1.68 2009/06/22 05:39:28 dtucker Exp $ */
+/* $OpenBSD: monitor_wrap.c,v 1.69 2010/03/07 11:57:13 dtucker Exp $ */
 /*
  * Copyright 2002 Niels Provos <provos@citi.umich.edu>
  * Copyright 2002 Markus Friedl <markus@openbsd.org>
@@ -347,19 +347,6 @@ mm_auth_rhosts_rsa_key_allowed(struct passwd *pw, char *user,
        return (ret);
 }
 
-static void
-mm_send_debug(Buffer *m)
-{
-       char *msg;
-
-       while (buffer_len(m)) {
-               msg = buffer_get_string(m, NULL);
-               debug3("%s: Sending debug: %s", __func__, msg);
-               packet_send_debug("%s", msg);
-               xfree(msg);
-       }
-}
-
 int
 mm_key_allowed(enum mm_keytype type, char *user, char *host, Key *key)
 {
@@ -393,9 +380,6 @@ mm_key_allowed(enum mm_keytype type, char *user, char *host, Key *key)
        have_forced = buffer_get_int(&m);
        forced_command = have_forced ? xstrdup("true") : NULL;
 
-       /* Send potential debug messages */
-       mm_send_debug(&m);
-
        buffer_free(&m);
 
        return (allowed);
@@ -1085,7 +1069,6 @@ mm_auth_rsa_key_allowed(struct passwd *pw, BIGNUM *client_n, Key **rkey)
                *rkey = key;
                xfree(blob);
        }
-       mm_send_debug(&m);
        buffer_free(&m);
 
        return (allowed);
index 79f8376..5c3857e 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: mux.c,v 1.7 2008/06/13 17:21:20 dtucker Exp $ */
+/* $OpenBSD: mux.c,v 1.21 2010/06/25 23:15:36 djm Exp $ */
 /*
  * Copyright (c) 2002-2008 Damien Miller <djm@openbsd.org>
  *
 
 /* ssh session multiplexing support */
 
-#include "includes.h"
-
 /*
  * TODO:
- *   1. partial reads in muxserver_accept_control (maybe make channels
- *      from accepted connections)
- *   2. Better signalling from master to slave, especially passing of
+ *   - Better signalling from master to slave, especially passing of
  *      error messages
- *   3. Better fall-back from mux slave error to new connection.
- *   3. Add/delete forwardings via slave
- *   4. ExitOnForwardingFailure (after #3 obviously)
- *   5. Maybe extension mechanisms for multi-X11/multi-agent forwarding
- *   6. Document the mux mini-protocol somewhere.
- *   7. Support ~^Z in mux slaves.
- *   8. Inspect or control sessions in master.
- *   9. If we ever support the "signal" channel request, send signals on
- *      sessions in master.
+ *   - Better fall-back from mux slave error to new connection.
+ *   - ExitOnForwardingFailure
+ *   - Maybe extension mechanisms for multi-X11/multi-agent forwarding
+ *   - Support ~^Z in mux slaves.
+ *   - Inspect or control sessions in master.
+ *   - If we ever support the "signal" channel request, send signals on
+ *     sessions in master.
  */
 
+#include "includes.h"
+
 #include <sys/types.h>
 #include <sys/param.h>
 #include <sys/stat.h>
 #include <paths.h>
 #endif
 
+#ifdef HAVE_POLL_H
+#include <poll.h>
+#else
+# ifdef HAVE_SYS_POLL_H
+#  include <sys/poll.h>
+# endif
+#endif
+
 #ifdef HAVE_UTIL_H
 # include <util.h>
 #endif
@@ -67,6 +71,7 @@
 #include "xmalloc.h"
 #include "log.h"
 #include "ssh.h"
+#include "ssh2.h"
 #include "pathnames.h"
 #include "misc.h"
 #include "match.h"
 
 /* from ssh.c */
 extern int tty_flag;
+extern int force_tty_flag;
 extern Options options;
 extern int stdin_null_flag;
 extern char *host;
-int subsystem_flag;
+extern int subsystem_flag;
 extern Buffer command;
+extern volatile sig_atomic_t quit_pending;
+extern char *stdio_forward_host;
+extern int stdio_forward_port;
 
 /* Context for session open confirmation callback */
 struct mux_session_confirm_ctx {
-       int want_tty;
-       int want_subsys;
-       int want_x_fwd;
-       int want_agent_fwd;
+       u_int want_tty;
+       u_int want_subsys;
+       u_int want_x_fwd;
+       u_int want_agent_fwd;
        Buffer cmd;
        char *term;
        struct termios tio;
        char **env;
+       u_int rid;
+};
+
+/* Context for global channel callback */
+struct mux_channel_confirm_ctx {
+       u_int cid;      /* channel id */
+       u_int rid;      /* request id */
+       int fid;        /* forward id */
 };
 
 /* fd to control socket */
 int muxserver_sock = -1;
 
+/* client request id */
+u_int muxclient_request_id = 0;
+
 /* Multiplexing control command */
 u_int muxclient_command = 0;
 
@@ -112,269 +132,244 @@ static volatile sig_atomic_t muxclient_terminate = 0;
 /* PID of multiplex server */
 static u_int muxserver_pid = 0;
 
+static Channel *mux_listener_channel = NULL;
 
-/* ** Multiplexing master support */
-
-/* Prepare a mux master to listen on a Unix domain socket. */
-void
-muxserver_listen(void)
-{
-       struct sockaddr_un addr;
-       mode_t old_umask;
-       int addr_len;
-
-       if (options.control_path == NULL ||
-           options.control_master == SSHCTL_MASTER_NO)
-               return;
-
-       debug("setting up multiplex master socket");
-
-       memset(&addr, '\0', sizeof(addr));
-       addr.sun_family = AF_UNIX;
-       addr_len = offsetof(struct sockaddr_un, sun_path) +
-           strlen(options.control_path) + 1;
-
-       if (strlcpy(addr.sun_path, options.control_path,
-           sizeof(addr.sun_path)) >= sizeof(addr.sun_path))
-               fatal("ControlPath too long");
+struct mux_master_state {
+       int hello_rcvd;
+};
 
-       if ((muxserver_sock = socket(PF_UNIX, SOCK_STREAM, 0)) < 0)
-               fatal("%s socket(): %s", __func__, strerror(errno));
+/* mux protocol messages */
+#define MUX_MSG_HELLO          0x00000001
+#define MUX_C_NEW_SESSION      0x10000002
+#define MUX_C_ALIVE_CHECK      0x10000004
+#define MUX_C_TERMINATE                0x10000005
+#define MUX_C_OPEN_FWD         0x10000006
+#define MUX_C_CLOSE_FWD                0x10000007
+#define MUX_C_NEW_STDIO_FWD    0x10000008
+#define MUX_S_OK               0x80000001
+#define MUX_S_PERMISSION_DENIED        0x80000002
+#define MUX_S_FAILURE          0x80000003
+#define MUX_S_EXIT_MESSAGE     0x80000004
+#define MUX_S_ALIVE            0x80000005
+#define MUX_S_SESSION_OPENED   0x80000006
+#define MUX_S_REMOTE_PORT      0x80000007
+
+/* type codes for MUX_C_OPEN_FWD and MUX_C_CLOSE_FWD */
+#define MUX_FWD_LOCAL   1
+#define MUX_FWD_REMOTE  2
+#define MUX_FWD_DYNAMIC 3
+
+static void mux_session_confirm(int, int, void *);
+
+static int process_mux_master_hello(u_int, Channel *, Buffer *, Buffer *);
+static int process_mux_new_session(u_int, Channel *, Buffer *, Buffer *);
+static int process_mux_alive_check(u_int, Channel *, Buffer *, Buffer *);
+static int process_mux_terminate(u_int, Channel *, Buffer *, Buffer *);
+static int process_mux_open_fwd(u_int, Channel *, Buffer *, Buffer *);
+static int process_mux_close_fwd(u_int, Channel *, Buffer *, Buffer *);
+static int process_mux_stdio_fwd(u_int, Channel *, Buffer *, Buffer *);
+
+static const struct {
+       u_int type;
+       int (*handler)(u_int, Channel *, Buffer *, Buffer *);
+} mux_master_handlers[] = {
+       { MUX_MSG_HELLO, process_mux_master_hello },
+       { MUX_C_NEW_SESSION, process_mux_new_session },
+       { MUX_C_ALIVE_CHECK, process_mux_alive_check },
+       { MUX_C_TERMINATE, process_mux_terminate },
+       { MUX_C_OPEN_FWD, process_mux_open_fwd },
+       { MUX_C_CLOSE_FWD, process_mux_close_fwd },
+       { MUX_C_NEW_STDIO_FWD, process_mux_stdio_fwd },
+       { 0, NULL }
+};
 
-       old_umask = umask(0177);
-       if (bind(muxserver_sock, (struct sockaddr *)&addr, addr_len) == -1) {
-               muxserver_sock = -1;
-               if (errno == EINVAL || errno == EADDRINUSE) {
-                       error("ControlSocket %s already exists, "
-                           "disabling multiplexing", options.control_path);
-                       close(muxserver_sock);
-                       muxserver_sock = -1;
-                       xfree(options.control_path);
-                       options.control_path = NULL;
-                       options.control_master = SSHCTL_MASTER_NO;
-                       return;
-               } else
-                       fatal("%s bind(): %s", __func__, strerror(errno));
+/* Cleanup callback fired on closure of mux slave _session_ channel */
+/* ARGSUSED */
+static void
+mux_master_session_cleanup_cb(int cid, void *unused)
+{
+       Channel *cc, *c = channel_by_id(cid);
+
+       debug3("%s: entering for channel %d", __func__, cid);
+       if (c == NULL)
+               fatal("%s: channel_by_id(%i) == NULL", __func__, cid);
+       if (c->ctl_chan != -1) {
+               if ((cc = channel_by_id(c->ctl_chan)) == NULL)
+                       fatal("%s: channel %d missing control channel %d",
+                           __func__, c->self, c->ctl_chan);
+               c->ctl_chan = -1;
+               cc->remote_id = -1;
+               chan_rcvd_oclose(cc);
        }
-       umask(old_umask);
-
-       if (listen(muxserver_sock, 64) == -1)
-               fatal("%s listen(): %s", __func__, strerror(errno));
-
-       set_nonblock(muxserver_sock);
+       channel_cancel_cleanup(c->self);
 }
 
-/* Callback on open confirmation in mux master for a mux client session. */
+/* Cleanup callback fired on closure of mux slave _control_ channel */
+/* ARGSUSED */
 static void
-mux_session_confirm(int id, void *arg)
+mux_master_control_cleanup_cb(int cid, void *unused)
 {
-       struct mux_session_confirm_ctx *cctx = arg;
-       const char *display;
-       Channel *c;
-       int i;
-
-       if (cctx == NULL)
-               fatal("%s: cctx == NULL", __func__);
-       if ((c = channel_lookup(id)) == NULL)
-               fatal("%s: no channel for id %d", __func__, id);
-
-       display = getenv("DISPLAY");
-       if (cctx->want_x_fwd && 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, &proto, &data);
-               /* Request forwarding with authentication spoofing. */
-               debug("Requesting X11 forwarding with authentication spoofing.");
-               x11_request_forwarding_with_spoofing(id, display, proto, data);
-               /* XXX wait for reply */
-       }
-
-       if (cctx->want_agent_fwd && options.forward_agent) {
-               debug("Requesting authentication agent forwarding.");
-               channel_request_start(id, "auth-agent-req@openssh.com", 0);
-               packet_send();
-       }
-
-       client_session2_setup(id, cctx->want_tty, cctx->want_subsys,
-           cctx->term, &cctx->tio, c->rfd, &cctx->cmd, cctx->env);
-
-       c->open_confirm_ctx = NULL;
-       buffer_free(&cctx->cmd);
-       xfree(cctx->term);
-       if (cctx->env != NULL) {
-               for (i = 0; cctx->env[i] != NULL; i++)
-                       xfree(cctx->env[i]);
-               xfree(cctx->env);
+       Channel *sc, *c = channel_by_id(cid);
+
+       debug3("%s: entering for channel %d", __func__, cid);
+       if (c == NULL)
+               fatal("%s: channel_by_id(%i) == NULL", __func__, cid);
+       if (c->remote_id != -1) {
+               if ((sc = channel_by_id(c->remote_id)) == NULL)
+                       fatal("%s: channel %d missing session channel %d",
+                           __func__, c->self, c->remote_id);
+               c->remote_id = -1;
+               sc->ctl_chan = -1;
+               if (sc->type != SSH_CHANNEL_OPEN) {
+                       debug2("%s: channel %d: not open", __func__, sc->self);
+                       chan_mark_dead(sc);
+               } else {
+                       if (sc->istate == CHAN_INPUT_OPEN)
+                               chan_read_failed(sc);
+                       if (sc->ostate == CHAN_OUTPUT_OPEN)
+                               chan_write_failed(sc);
+               }
        }
-       xfree(cctx);
+       channel_cancel_cleanup(c->self);
 }
 
-/*
- * Accept a connection on the mux master socket and process the
- * client's request. Returns flag indicating whether mux master should
- * begin graceful close.
- */
-int
-muxserver_accept_control(void)
+/* Check mux client environment variables before passing them to mux master. */
+static int
+env_permitted(char *env)
 {
-       Buffer m;
-       Channel *c;
-       int client_fd, new_fd[3], ver, allowed, window, packetmax;
-       socklen_t addrlen;
-       struct sockaddr_storage addr;
-       struct mux_session_confirm_ctx *cctx;
-       char *cmd;
-       u_int i, j, len, env_len, mux_command, flags, escape_char;
-       uid_t euid;
-       gid_t egid;
-       int start_close = 0;
-
-       /*
-        * Accept connection on control socket
-        */
-       memset(&addr, 0, sizeof(addr));
-       addrlen = sizeof(addr);
-       if ((client_fd = accept(muxserver_sock,
-           (struct sockaddr*)&addr, &addrlen)) == -1) {
-               error("%s accept: %s", __func__, strerror(errno));
-               return 0;
-       }
+       int i, ret;
+       char name[1024], *cp;
 
-       if (getpeereid(client_fd, &euid, &egid) < 0) {
-               error("%s getpeereid failed: %s", __func__, strerror(errno));
-               close(client_fd);
+       if ((cp = strchr(env, '=')) == NULL || cp == env)
                return 0;
-       }
-       if ((euid != 0) && (getuid() != euid)) {
-               error("control mode uid mismatch: peer euid %u != uid %u",
-                   (u_int) euid, (u_int) getuid());
-               close(client_fd);
+       ret = snprintf(name, sizeof(name), "%.*s", (int)(cp - env), env);
+       if (ret <= 0 || (size_t)ret >= sizeof(name)) {
+               error("env_permitted: name '%.100s...' too long", env);
                return 0;
        }
 
-       /* XXX handle asynchronously */
-       unset_nonblock(client_fd);
+       for (i = 0; i < options.num_send_env; i++)
+               if (match_pattern(name, options.send_env[i]))
+                       return 1;
 
-       /* Read command */
-       buffer_init(&m);
-       if (ssh_msg_recv(client_fd, &m) == -1) {
-               error("%s: client msg_recv failed", __func__);
-               close(client_fd);
-               buffer_free(&m);
-               return 0;
+       return 0;
+}
+
+/* Mux master protocol message handlers */
+
+static int
+process_mux_master_hello(u_int rid, Channel *c, Buffer *m, Buffer *r)
+{
+       u_int ver;
+       struct mux_master_state *state = (struct mux_master_state *)c->mux_ctx;
+
+       if (state == NULL)
+               fatal("%s: channel %d: c->mux_ctx == NULL", __func__, c->self);
+       if (state->hello_rcvd) {
+               error("%s: HELLO received twice", __func__);
+               return -1;
        }
-       if ((ver = buffer_get_char(&m)) != SSHMUX_VER) {
-               error("%s: wrong client version %d", __func__, ver);
-               buffer_free(&m);
-               close(client_fd);
-               return 0;
+       if (buffer_get_int_ret(&ver, m) != 0) {
+ malf:
+               error("%s: malformed message", __func__);
+               return -1;
        }
+       if (ver != SSHMUX_VER) {
+               error("Unsupported multiplexing protocol version %d "
+                   "(expected %d)", ver, SSHMUX_VER);
+               return -1;
+       }
+       debug2("%s: channel %d slave version %u", __func__, c->self, ver);
 
-       allowed = 1;
-       mux_command = buffer_get_int(&m);
-       flags = buffer_get_int(&m);
-
-       buffer_clear(&m);
+       /* No extensions are presently defined */
+       while (buffer_len(m) > 0) {
+               char *name = buffer_get_string_ret(m, NULL);
+               char *value = buffer_get_string_ret(m, NULL);
 
-       switch (mux_command) {
-       case SSHMUX_COMMAND_OPEN:
-               if (options.control_master == SSHCTL_MASTER_ASK ||
-                   options.control_master == SSHCTL_MASTER_AUTO_ASK)
-                       allowed = ask_permission("Allow shared connection "
-                           "to %s? ", host);
-               /* continue below */
-               break;
-       case SSHMUX_COMMAND_TERMINATE:
-               if (options.control_master == SSHCTL_MASTER_ASK ||
-                   options.control_master == SSHCTL_MASTER_AUTO_ASK)
-                       allowed = ask_permission("Terminate shared connection "
-                           "to %s? ", host);
-               if (allowed)
-                       start_close = 1;
-               /* FALLTHROUGH */
-       case SSHMUX_COMMAND_ALIVE_CHECK:
-               /* Reply for SSHMUX_COMMAND_TERMINATE and ALIVE_CHECK */
-               buffer_clear(&m);
-               buffer_put_int(&m, allowed);
-               buffer_put_int(&m, getpid());
-               if (ssh_msg_send(client_fd, SSHMUX_VER, &m) == -1) {
-                       error("%s: client msg_send failed", __func__);
-                       close(client_fd);
-                       buffer_free(&m);
-                       return start_close;
+               if (name == NULL || value == NULL) {
+                       if (name != NULL)
+                               xfree(name);
+                       goto malf;
                }
-               buffer_free(&m);
-               close(client_fd);
-               return start_close;
-       default:
-               error("Unsupported command %d", mux_command);
-               buffer_free(&m);
-               close(client_fd);
-               return 0;
+               debug2("Unrecognised slave extension \"%s\"", name);
+               xfree(name);
+               xfree(value);
        }
+       state->hello_rcvd = 1;
+       return 0;
+}
+
+static int
+process_mux_new_session(u_int rid, Channel *c, Buffer *m, Buffer *r)
+{
+       Channel *nc;
+       struct mux_session_confirm_ctx *cctx;
+       char *reserved, *cmd, *cp;
+       u_int i, j, len, env_len, escape_char, window, packetmax;
+       int new_fd[3];
 
        /* Reply for SSHMUX_COMMAND_OPEN */
-       buffer_clear(&m);
-       buffer_put_int(&m, allowed);
-       buffer_put_int(&m, getpid());
-       if (ssh_msg_send(client_fd, SSHMUX_VER, &m) == -1) {
-               error("%s: client msg_send failed", __func__);
-               close(client_fd);
-               buffer_free(&m);
-               return 0;
+       cctx = xcalloc(1, sizeof(*cctx));
+       cctx->term = NULL;
+       cctx->rid = rid;
+       cmd = reserved = NULL;
+       if ((reserved = buffer_get_string_ret(m, NULL)) == NULL ||
+           buffer_get_int_ret(&cctx->want_tty, m) != 0 ||
+           buffer_get_int_ret(&cctx->want_x_fwd, m) != 0 ||
+           buffer_get_int_ret(&cctx->want_agent_fwd, m) != 0 ||
+           buffer_get_int_ret(&cctx->want_subsys, m) != 0 ||
+           buffer_get_int_ret(&escape_char, m) != 0 ||
+           (cctx->term = buffer_get_string_ret(m, &len)) == NULL ||
+           (cmd = buffer_get_string_ret(m, &len)) == NULL) {
+ malf:
+               if (cmd != NULL)
+                       xfree(cmd);
+               if (reserved != NULL)
+                       xfree(reserved);
+               if (cctx->term != NULL)
+                       xfree(cctx->term);
+               error("%s: malformed message", __func__);
+               return -1;
        }
-
-       if (!allowed) {
-               error("Refused control connection");
-               close(client_fd);
-               buffer_free(&m);
-               return 0;
+       xfree(reserved);
+       reserved = NULL;
+
+       cctx->env = NULL;
+       env_len = 0;
+       while (buffer_len(m) > 0) {
+#define MUX_MAX_ENV_VARS       4096
+               if ((cp = buffer_get_string_ret(m, &len)) == NULL) {
+                       xfree(cmd);
+                       goto malf;
+               }
+               if (!env_permitted(cp)) {
+                       xfree(cp);
+                       continue;
+               }
+               cctx->env = xrealloc(cctx->env, env_len + 2,
+                   sizeof(*cctx->env));
+               cctx->env[env_len++] = cp;
+               cctx->env[env_len] = NULL;
+               if (env_len > MUX_MAX_ENV_VARS) {
+                       error(">%d environment variables received, ignoring "
+                           "additional", MUX_MAX_ENV_VARS);
+                       break;
+               }
        }
 
-       buffer_clear(&m);
-       if (ssh_msg_recv(client_fd, &m) == -1) {
-               error("%s: client msg_recv failed", __func__);
-               close(client_fd);
-               buffer_free(&m);
-               return 0;
-       }
-       if ((ver = buffer_get_char(&m)) != SSHMUX_VER) {
-               error("%s: wrong client version %d", __func__, ver);
-               buffer_free(&m);
-               close(client_fd);
-               return 0;
-       }
+       debug2("%s: channel %d: request tty %d, X %d, agent %d, subsys %d, "
+           "term \"%s\", cmd \"%s\", env %u", __func__, c->self,
+           cctx->want_tty, cctx->want_x_fwd, cctx->want_agent_fwd,
+           cctx->want_subsys, cctx->term, cmd, env_len);
 
-       cctx = xcalloc(1, sizeof(*cctx));
-       cctx->want_tty = (flags & SSHMUX_FLAG_TTY) != 0;
-       cctx->want_subsys = (flags & SSHMUX_FLAG_SUBSYS) != 0;
-       cctx->want_x_fwd = (flags & SSHMUX_FLAG_X11_FWD) != 0;
-       cctx->want_agent_fwd = (flags & SSHMUX_FLAG_AGENT_FWD) != 0;
-       cctx->term = buffer_get_string(&m, &len);
-       escape_char = buffer_get_int(&m);
-
-       cmd = buffer_get_string(&m, &len);
        buffer_init(&cctx->cmd);
        buffer_append(&cctx->cmd, cmd, strlen(cmd));
-
-       env_len = buffer_get_int(&m);
-       env_len = MIN(env_len, 4096);
-       debug3("%s: receiving %d env vars", __func__, env_len);
-       if (env_len != 0) {
-               cctx->env = xcalloc(env_len + 1, sizeof(*cctx->env));
-               for (i = 0; i < env_len; i++)
-                       cctx->env[i] = buffer_get_string(&m, &len);
-               cctx->env[i] = NULL;
-       }
-
-       debug2("%s: accepted tty %d, subsys %d, cmd %s", __func__,
-           cctx->want_tty, cctx->want_subsys, cmd);
        xfree(cmd);
+       cmd = NULL;
 
        /* Gather fds from client */
        for(i = 0; i < 3; i++) {
-               if ((new_fd[i] = mm_receive_fd(client_fd)) == -1) {
+               if ((new_fd[i] = mm_receive_fd(c->sock)) == -1) {
                        error("%s: failed to receive fd %d from slave",
                            __func__, i);
                        for (j = 0; j < i; j++)
@@ -385,37 +380,56 @@ muxserver_accept_control(void)
                                xfree(cctx->env);
                        xfree(cctx->term);
                        buffer_free(&cctx->cmd);
-                       close(client_fd);
                        xfree(cctx);
-          &n