From davi at leals.com Wed Jan 6 02:01:11 2010 From: davi at leals.com (Davi Diaz) Date: Tue, 5 Jan 2010 15:01:11 +0000 Subject: OpenSSH daemon security bug? Message-ID: <201001051501.11753.davi@leals.com> A co-worker argues we can login using only password to a "ssh-key restricted host (PasswordAuthentication no)", without being asked by any passphase; just by putting a key (no need to be the private key) on another password-based host. It that true? I do not think so. I would name that as an "important OpenSSH daemon security bug". That is because I think it is not true. co-worker wrote: > You cannot distinguish passphrased keys from passphraseless ones. I think the OpenSSH daemon will take care to ask for a key passphrase before using a key to open an encrypted channel. A ssh key which requires a ssh passphrase to be usable can not be used to open a ssh connection if such ssh passphrase is not provided, as it is part of the encryption algorithm. I know we can create ssh keys without passphrases (useful for unattended backups, scripts and so on). However our users will be told not to do that, of course, as they are told not to create weak passwords. co-worker wrote: > I am all for encouraging key-based logins, but I think disabling > password logins completely actually reduces security. Of course I disagree because I think such "OpenSSH daemon security bug" is not a true story. It is a false one. What do you think? From davi at leals.com Wed Jan 6 02:37:45 2010 From: davi at leals.com (Davi Diaz) Date: Tue, 5 Jan 2010 15:37:45 +0000 Subject: OpenSSH daemon security bug? In-Reply-To: <201001051501.11753.davi@leals.com> References: <201001051501.11753.davi@leals.com> Message-ID: <201001051537.46130.davi@leals.com> > co-worker wrote: > > You cannot distinguish passphrased keys from passphraseless ones. Is there any way to detect from sshd whether a private key has a passphrase or not? That would allow add a configuration option to be able to reject keys which does not has passphrases? That would be a security enhancement for OpenSSH. From aris.adamantiadis at belnet.be Wed Jan 6 02:41:21 2010 From: aris.adamantiadis at belnet.be (Aris Adamantiadis) Date: Tue, 05 Jan 2010 16:41:21 +0100 Subject: OpenSSH daemon security bug? In-Reply-To: <201001051501.11753.davi@leals.com> References: <201001051501.11753.davi@leals.com> Message-ID: <4B435DA1.8040808@belnet.be> Hello, Password authentication and password-protected keys are two different things. Using password-protected keys, the decryption of the private key is done on client side (to protect the confidentiality of the key), and there is nothing in the SSH protocol which could stop the behavior of accepting "less secure keys because they were stored in clear". It's not less secure than writing the utterly complex password in a clear text file because you can't remember it. So the quote from your coworker >> You cannot distinguish passphrased keys from passphraseless ones. Is true. >> I am all for encouraging key-based logins, but I think disabling >> password logins completely actually reduces security. Possibly, especially when you need to authenticate from a host whose security is not known (cyber-caf?, friends station, ...). A OTP or 2-factor authentication is preferred. Regards, Aris Davi Diaz a ?crit : > A co-worker argues we can login using only password to a "ssh-key restricted > host (PasswordAuthentication no)", without being asked by any passphase; just > by putting a key (no need to be the private key) on another password-based > host. > > It that true? I do not think so. I would name that as an "important OpenSSH > daemon security bug". That is because I think it is not true. > > > co-worker wrote: >> You cannot distinguish passphrased keys from passphraseless ones. > > I think the OpenSSH daemon will take care to ask for a key passphrase before > using a key to open an encrypted channel. > > A ssh key which requires a ssh passphrase to be usable can not be used to open > a ssh connection if such ssh passphrase is not provided, as it is part of the > encryption algorithm. > > I know we can create ssh keys without passphrases (useful for unattended > backups, scripts and so on). However our users will be told not to do that, > of course, as they are told not to create weak passwords. > > > co-worker wrote: >> I am all for encouraging key-based logins, but I think disabling >> password logins completely actually reduces security. > > Of course I disagree because I think such "OpenSSH daemon security bug" is not > a true story. It is a false one. > > What do you think? > _______________________________________________ > openssh-unix-dev mailing list > openssh-unix-dev at mindrot.org > https://lists.mindrot.org/mailman/listinfo/openssh-unix-dev > -- Aris Adamantiadis -- BELNET, Customer Relations Technical Advisor t: ++32 2 790 33 33 Dept: customer at belnet.be Contact: http://www.belnet.be/contact.html From mouring at eviladmin.org Wed Jan 6 03:11:38 2010 From: mouring at eviladmin.org (Ben Lindstrom) Date: Tue, 5 Jan 2010 10:11:38 -0600 Subject: OpenSSH daemon security bug? In-Reply-To: <201001051537.46130.davi@leals.com> References: <201001051501.11753.davi@leals.com> <201001051537.46130.davi@leals.com> Message-ID: <03C8417F-A594-43BB-A543-A96CA477E96E@eviladmin.org> On Jan 5, 2010, at 9:37 AM, Davi Diaz wrote: >> co-worker wrote: >>> You cannot distinguish passphrased keys from passphraseless ones. > > Is there any way to detect from sshd whether a private key has a passphrase or > not? > > That would allow add a configuration option to be able to reject keys which > does not has passphrases? That would be a security enhancement for OpenSSH. In a word "no"... Because the server never sees the private key. The client handles the decrypting if there is a need. - Ben From maniac.nl at gmail.com Wed Jan 6 02:21:34 2010 From: maniac.nl at gmail.com (Mark Janssen) Date: Tue, 5 Jan 2010 16:21:34 +0100 Subject: OpenSSH daemon security bug? In-Reply-To: <201001051501.11753.davi@leals.com> References: <201001051501.11753.davi@leals.com> Message-ID: <531e3e4c1001050721h56f5b5f0n28a1aea6fa9f4dd6@mail.gmail.com> On Tue, Jan 5, 2010 at 4:01 PM, Davi Diaz wrote: > A co-worker argues we can login using only password to a "ssh-key restricted > host (PasswordAuthentication no)", without being asked by any passphase; just > by putting a key (no need to be the private key) on another password-based > host. > > It that true? I do not think so. ?I would name that as an "important OpenSSH > daemon security bug". That is because I think it is not true. You can only login using keys if the public key is included in the 'authorized_keys' file on the server. The ssh client will read the private key (passphrased or not, ask for a passphrase if needed (or read from an agent)). The server has no way of knowing if the key had a passphrase (was encrypted), as it never sees the private key. The private key is only used for authentication/encryption on the client-side. > co-worker wrote: >> You cannot distinguish passphrased keys from passphraseless ones. True (server never sees the key, only the result of computations on the decrypted key) > I think the OpenSSH daemon will take care to ask for a key passphrase before > using a key to open an encrypted channel. False, the client handles keys > A ssh key which requires a ssh passphrase to be usable can not be used to open > a ssh connection if such ssh passphrase is not provided, as it is part of the > encryption algorithm. False > I know we can create ssh keys without passphrases (useful for unattended > backups, scripts and so on). ?However our users will be told not to do that, > of course, as they are told not to create weak passwords. > > > co-worker wrote: >> I am all for encouraging key-based logins, but I think disabling >> password logins completely actually reduces security. I must agree here, while keys are better then passwords, it's impossible to enforce passphrase quality on keys, while it is possible to enforce some quality on passwords. -- Mark Janssen -- maniac(at)maniac.nl -- pgp: 0x357D2178 | ,''`. | Unix / Linux Open-Source and Internet Consultant @ Snow.nl | : :' : | Maniac.nl MarkJanssen.nl NerdNet.nl Unix.nl | `. `' | Skype: markmjanssen ICQ: 129696007 irc: FooBar on undernet | `- | From davi at leals.com Wed Jan 6 04:13:41 2010 From: davi at leals.com (Davi Diaz) Date: Tue, 5 Jan 2010 17:13:41 +0000 Subject: OpenSSH daemon security bug? In-Reply-To: <531e3e4c1001050721h56f5b5f0n28a1aea6fa9f4dd6@mail.gmail.com> References: <201001051501.11753.davi@leals.com> <531e3e4c1001050721h56f5b5f0n28a1aea6fa9f4dd6@mail.gmail.com> Message-ID: <201001051713.42255.davi@leals.com> Mark Janssen wrote: > > co-worker wrote: > >> I am all for encouraging key-based logins, but I think disabling > >> password logins completely actually reduces security. > > I must agree here, while keys are better then passwords, it's > impossible to enforce passphrase quality on keys, while it is possible > to enforce some quality on passwords. OK, If all users agree about following the security policy I would be in favour to allow ssh-key access, blocking the password one by being less secure. If users does not agree, I would be even against adding ssh-key access to the current password based access because ssh-key without a good key policy management is less secure even if the public key has to be included in the 'authorized_keys' file on the server. From dkg at fifthhorseman.net Wed Jan 6 04:32:53 2010 From: dkg at fifthhorseman.net (Daniel Kahn Gillmor) Date: Tue, 05 Jan 2010 12:32:53 -0500 Subject: OpenSSH daemon security bug? In-Reply-To: <201001051713.42255.davi@leals.com> References: <201001051501.11753.davi@leals.com> <531e3e4c1001050721h56f5b5f0n28a1aea6fa9f4dd6@mail.gmail.com> <201001051713.42255.davi@leals.com> Message-ID: <4B4377C5.2060006@fifthhorseman.net> On 01/05/2010 12:13 PM, Davi Diaz wrote: > OK, If all users agree about following the security policy I would be in > favour to allow ssh-key access, blocking the password one by being less > secure. Recent versions of OpenSSH allow you to set your decisions about who gets key-based (or password-based) access on a more fine-grained level. For more info, see the Match keyword in sshd_config(8). > If users does not agree, I would be even against adding ssh-key access to the > current password based access because ssh-key without a good key policy > management is less secure even if the public key has to be included in > the 'authorized_keys' file on the server. If your users are unwilling or unable to follow your security policy, then you have problems that cannot be technologically resolved, unfortunately :( User education is tough, but there is no magically secure (and still-useful) tool without it. --dkg -------------- next part -------------- A non-text attachment was scrubbed... Name: signature.asc Type: application/pgp-signature Size: 891 bytes Desc: OpenPGP digital signature URL: From mstone at mathom.us Wed Jan 6 02:14:34 2010 From: mstone at mathom.us (Michael Stone) Date: Tue, 05 Jan 2010 10:14:34 -0500 Subject: OpenSSH daemon security bug? In-Reply-To: <201001051501.11753.davi@leals.com> References: <201001051501.11753.davi@leals.com> Message-ID: <49ba991c-fa0c-11de-9b6a-001cc0cda50c@msgid.mathom.us> It's true that for some threats a poorly managed ssh private key is weaker authentication than a well managed password. Trying to fix poor password management (brute force ssh password guessing doesn't work with well managed password policies) by mandating the use of ssh keys is generally a recipe for disaster. (If you can't enforce a password policy why on earth do you think you can enforce a key management policy?) Both mechanisms have their strengths and weaknesses, and neither should be chosen over the other unless you understand what those are. In many cases the ideal option would be *both* a certificate *and* a password. Mike Stone From dkg at fifthhorseman.net Wed Jan 6 04:25:26 2010 From: dkg at fifthhorseman.net (Daniel Kahn Gillmor) Date: Tue, 05 Jan 2010 12:25:26 -0500 Subject: OpenSSH daemon security bug? In-Reply-To: <531e3e4c1001050721h56f5b5f0n28a1aea6fa9f4dd6@mail.gmail.com> References: <201001051501.11753.davi@leals.com> <531e3e4c1001050721h56f5b5f0n28a1aea6fa9f4dd6@mail.gmail.com> Message-ID: <4B437606.9020601@fifthhorseman.net> On 01/05/2010 10:21 AM, Mark Janssen wrote: > On Tue, Jan 5, 2010 at 4:01 PM, Davi Diaz wrote: >> co-worker wrote: >>> I am all for encouraging key-based logins, but I think disabling >>> password logins completely actually reduces security. > > I must agree here, while keys are better then passwords, it's > impossible to enforce passphrase quality on keys, while it is possible > to enforce some quality on passwords. > i don't think you're comparing the same thing, though. You can make sure it's a really really strong password, but it's still *not* possible to enforce that your users keep their password safe. If you're worried that your users might leave an unprotected key lying around, you should *also* be worried that those same users might send their password via e-mail (even if it's just "to themselves as a reminder"), or write it in a cleartext file on their computer, reuse it for their amazon account, for their blog, etc. At some level, you have to trust your users if they're going to use your system. And have good backups, easy recovery, and regular user education about good practices, of course ;) --dkg -------------- next part -------------- A non-text attachment was scrubbed... Name: signature.asc Type: application/pgp-signature Size: 887 bytes Desc: OpenPGP digital signature URL: From andreas at zzlevo.net Wed Jan 6 05:27:00 2010 From: andreas at zzlevo.net (Andreas Gunnarsson) Date: Tue, 5 Jan 2010 19:27:00 +0100 Subject: OpenSSH daemon security bug? In-Reply-To: <201001051713.42255.davi@leals.com> References: <201001051501.11753.davi@leals.com> <531e3e4c1001050721h56f5b5f0n28a1aea6fa9f4dd6@mail.gmail.com> <201001051713.42255.davi@leals.com> Message-ID: <20100105182700.GA23830@zzlevo.net> On Tue, Jan 05, 2010 at 05:13:41PM +0000, Davi Diaz wrote: > If users does not agree [on following the security policy], I would be > even against adding ssh-key access to the current password based > access because ssh-key without a good key policy management is less > secure even if the public key has to be included in the > 'authorized_keys' file on the server. Password authentication with a weak password is in itself no more secure than public key authentication where the private key is protected with a weak password. In the former case the attacker only needs to guess the password, in the latter case they also need to get the private key. Knowledge of the public key does not help an attacker. A potential risk with public key authentication is that if an attacker does find the encrypted private key they will be able to try passwords offline, without contacting the server until the correct one was guessed. On the other hand, an attacker who manages to get hold of the server's host key could be able to do a man-in-the-middle attack on users who log in using password authentication. Such an attack is not possible with public key authentication. And if the server is compromised, password authentication allows the attacker to collect users' passwords which most likely will be valid for other sites. If public key authentication is used, the attacker will not be able to collect the private keys (unless they are stored on the compromised system and the user unlocks it while logged on there of course). So which authentication method is best depends on the circumstances. Public key authentication with a well protected private key may be perfectly secure even without a passphrase. Automatic jobs often use passwordless private keys if they need to connect to another host, for example. But unless the private key is somehow made publicly available, I would say that public key authentication is "more secure" in most cases. Of course, for interactive use, a private key with a good passphrase is better than one with a week passphrase in almost all cases. Andreas From mstone at mathom.us Wed Jan 6 06:06:45 2010 From: mstone at mathom.us (Michael Stone) Date: Tue, 05 Jan 2010 14:06:45 -0500 Subject: OpenSSH daemon security bug? In-Reply-To: <4B437606.9020601@fifthhorseman.net> References: <201001051501.11753.davi@leals.com> <531e3e4c1001050721h56f5b5f0n28a1aea6fa9f4dd6@mail.gmail.com> <4B437606.9020601@fifthhorseman.net> Message-ID: <904050dc-fa2c-11de-9b6a-001cc0cda50c@msgid.mathom.us> On Tue, Jan 05, 2010 at 12:25:26PM -0500, you wrote: >i don't think you're comparing the same thing, though. You can make >sure it's a really really strong password, but it's still *not* possible >to enforce that your users keep their password safe. > >If you're worried that your users might leave an unprotected key lying >around, you should *also* be worried that those same users might send >their password via e-mail (even if it's just "to themselves as a >reminder"), or write it in a cleartext file on their computer, reuse it >for their amazon account, for their blog, etc. In my experience users have a much better understanding of what to do with a password than what to do with a key. It's also fairly trivial to do things like force password changes if compromise is suspected, etc., as infrastructure to do that is pretty common. While it's certainly possible to do that sort of thing with key management, it's much less common (especially among people who use keys "because they're more secure"). Also, it's worth noting that "well, people can mishandle passwords" isn't really a worthwhile argument. The question should be, "what threat are you trying to mitigate by using keys?" If you know what you're trying to do and why you're trying to do it, then you can have a rational discussion of the costs vs benefits of the two approaches. (IMO, there's no single "right answer" for everbody, which is why it needs to be thought about.) Mike Stone From Jefferson.Ogata at noaa.gov Wed Jan 6 05:48:27 2010 From: Jefferson.Ogata at noaa.gov (Jefferson Ogata) Date: Tue, 05 Jan 2010 18:48:27 +0000 Subject: OpenSSH daemon security bug? In-Reply-To: <531e3e4c1001050721h56f5b5f0n28a1aea6fa9f4dd6@mail.gmail.com> References: <201001051501.11753.davi@leals.com> <531e3e4c1001050721h56f5b5f0n28a1aea6fa9f4dd6@mail.gmail.com> Message-ID: <4B43897B.3050606@noaa.gov> On 2010-01-05 15:21, Mark Janssen wrote: > The server has no way of knowing if the key had a passphrase (was > encrypted), as it never sees the private key. The private key is only > used for authentication/encryption on the client-side. Actually the server could theoretically determine heuristically if the key has no passphrase (or if the user is using ssh-agent) by timing the key transaction. I've often thought it would be useful for sshd to have an option for requiring that there be a delay before each pubkey transaction for the purpose of assuring that a passphrase is being typed on the client side. Obviously someone could hack the client or agent to work around this, but it would still help, in my opinion, for particular, interactive-only situations where people are authenticating from an external origin. -- Jefferson Ogata NOAA Computer Incident Response Team (N-CIRT) "Never try to retrieve anything from a bear."--National Park Service From jamie.beverly at yahoo.com Wed Jan 6 06:06:28 2010 From: jamie.beverly at yahoo.com (Jamie Beverly) Date: Tue, 5 Jan 2010 11:06:28 -0800 (PST) Subject: OpenSSH daemon security bug? In-Reply-To: <4B437606.9020601@fifthhorseman.net> References: <201001051501.11753.davi@leals.com> <531e3e4c1001050721h56f5b5f0n28a1aea6fa9f4dd6@mail.gmail.com> <4B437606.9020601@fifthhorseman.net> Message-ID: <887124.18116.qm@web31809.mail.mud.yahoo.com> ----- Original Message ---- > From: Daniel Kahn Gillmor > To: openssh-unix-dev at mindrot.org > Sent: Tue, January 5, 2010 9:25:26 AM > Subject: Re: OpenSSH daemon security bug? > > On 01/05/2010 10:21 AM, Mark Janssen wrote: > > On Tue, Jan 5, 2010 at 4:01 PM, Davi Diaz wrote: > >> co-worker wrote: > >>> I am all for encouraging key-based logins, but I think disabling > >>> password logins completely actually reduces security. > > > > I must agree here, while keys are better then passwords, it's > > impossible to enforce passphrase quality on keys, while it is possible > > to enforce some quality on passwords. > > > i don't think you're comparing the same thing, though. You can make > sure it's a really really strong password, but it's still *not* possible > to enforce that your users keep their password safe. > > If you're worried that your users might leave an unprotected key lying > around, you should *also* be worried that those same users might send > their password via e-mail (even if it's just "to themselves as a > reminder"), or write it in a cleartext file on their computer, reuse it > for their amazon account, for their blog, etc. > > At some level, you have to trust your users if they're going to use your > system. And have good backups, easy recovery, and regular user > education about good practices, of course ;) > > --dkg More to the point, password authentication is fundamentally less secure than ssh public key authentication. The comparisons being made here are seemingly getting hung up on the word "password", and supposing that the weakness of one password is the same as the weakness of another. They are not. The password on a private key helps to protect that private key from being stolen. However, the most often exploited, and hence far greater risks are brute-force/dictionary attacks and password interception. Even a passwordless private key has between 768-4096 bits of entropy, which is roughly equivalent to a human-remembered passphrase of between 2000-10,000 characters. Good luck enforcing that password policy! And as to interception, private key exchanges never actually send the private key on the wire, instead they rely on a challenge/response using a randomly generated number. (essentially a packet containing a random number is encrypted to a public key, and that encrypted message is sent as the challenge; the client is then forced to decrypt that message to prove that it can decipher that number; if it helps by way of analogy, its like sending somebody a GPG encrypted email and then having them prove they have the key that can decrypt it) As dkg points out, you have to trust your users not to do stupid things, and more often than not, continue to find yourself disappointed when they do in fact, do stupid things. That said, there is NO case where password authentication is MORE, or even nearly AS secure as private key authentication. Public key authentication mitigates more risk.Period. From Jefferson.Ogata at noaa.gov Wed Jan 6 06:25:03 2010 From: Jefferson.Ogata at noaa.gov (Jefferson Ogata) Date: Tue, 05 Jan 2010 19:25:03 +0000 Subject: OpenSSH daemon security bug? In-Reply-To: <904050dc-fa2c-11de-9b6a-001cc0cda50c@msgid.mathom.us> References: <201001051501.11753.davi@leals.com> <531e3e4c1001050721h56f5b5f0n28a1aea6fa9f4dd6@mail.gmail.com> <4B437606.9020601@fifthhorseman.net> <904050dc-fa2c-11de-9b6a-001cc0cda50c@msgid.mathom.us> Message-ID: <4B43920F.7030502@noaa.gov> On 2010-01-05 19:06, Michael Stone wrote: > Also, it's worth noting that "well, people can mishandle passwords" > isn't really a worthwhile argument. The question should be, "what threat > are you trying to mitigate by using keys?" If you know what you're > trying to do and why you're trying to do it, then you can have a > rational discussion of the costs vs benefits of the two approaches. > (IMO, there's no single "right answer" for everbody, which is why it > needs to be thought about.) It's not "people can mishandle passwords". It's "people do mishandle passwords". For what it's worth, as an incident handler, I've witnessed a lot of cases of password guessing against sshd in my days. I haven't seen a single instance of someone stealing a passphrased pubkey and using that, let alone discovering the passphrase on a key; the only compromises I've seen that involve pubkeys are intruders using an unpassphrased key from the system on which it resides to get to a related system, generally by consulting .ssh/known_hosts. Of course, attacks against pubkeys are possible, but they almost never happen. The typical script kiddie who gets into your system has an ssh password guessing tool that he uses to scan outbound from the compromised box, and that's exactly how he got into the box. Of course, script kiddies aren't your only threat, but you might be surprised how often they are successful. One of the things that happens when PasswordAuthentication is enabled is that some lame sysadmin decides to set up some new software that requires a dedicated userid. So he creates the user and assigns it a weak password, with the intention of changing it "later" (never mind that the user didn't need a password at all). Of course, a couple of weeks later (if that) you start seeing the outbound ssh scans... For me, it's a no-brainer. Turn off password auth. -- Jefferson Ogata NOAA Computer Incident Response Team (N-CIRT) "Never try to retrieve anything from a bear."--National Park Service From jrollins at finestructure.net Wed Jan 6 07:19:09 2010 From: jrollins at finestructure.net (Jameson Rollins) Date: Tue, 5 Jan 2010 15:19:09 -0500 Subject: OpenSSH daemon security bug? In-Reply-To: <4B43897B.3050606@noaa.gov> References: <201001051501.11753.davi@leals.com> <531e3e4c1001050721h56f5b5f0n28a1aea6fa9f4dd6@mail.gmail.com> <4B43897B.3050606@noaa.gov> Message-ID: <20100105201909.GA11258@finestructure.net> On Tue, Jan 05, 2010 at 06:48:27PM +0000, Jefferson Ogata wrote: > Actually the server could theoretically determine heuristically if the > key has no passphrase (or if the user is using ssh-agent) by timing the > key transaction. I've often thought it would be useful for sshd to have > an option for requiring that there be a delay before each pubkey > transaction for the purpose of assuring that a passphrase is being typed > on the client side. Actually most agents cache the key in memory, and most don't require passwords to be typed in for every use, so I don't think this would work. jamie. -------------- next part -------------- A non-text attachment was scrubbed... Name: not available Type: application/pgp-signature Size: 836 bytes Desc: Digital signature URL: From mstone at mathom.us Wed Jan 6 06:18:03 2010 From: mstone at mathom.us (Michael Stone) Date: Tue, 05 Jan 2010 14:18:03 -0500 Subject: OpenSSH daemon security bug? In-Reply-To: <20100105182700.GA23830@zzlevo.net> References: <201001051501.11753.davi@leals.com> <531e3e4c1001050721h56f5b5f0n28a1aea6fa9f4dd6@mail.gmail.com> <201001051713.42255.davi@leals.com> <20100105182700.GA23830@zzlevo.net> Message-ID: <04d07070-fa2e-11de-9b6a-001cc0cda50c@msgid.mathom.us> On Tue, Jan 05, 2010 at 07:27:00PM +0100, you wrote: >Password authentication with a weak password is in itself no more secure >than public key authentication where the private key is protected with a >weak password. In the former case the attacker only needs to guess the >password, in the latter case they also need to get the private key. >Knowledge of the public key does not help an attacker. You can force the password complexity on the server side. You can't force the key management through technical means. That's a very important difference. Where this has proven to be a major headache in practice is when people have copies of their private keys scattered around on various machines, leading to intruders being able to jump from one machine to another. (Possibly from one organization to another, especially in collaborative environments like .edu's.) Private keys are seldom expired on a routine basis, so there may be forgotten copies lying about that haven't been used in years. (Should people do this? No. Does it happen? Yes. Can you prove none of your users have done it? Probably not.) Even with a strong password, there's a big difference between a passive offline attack against a key and an active brute force of a login password. Mike Stone From jamie.beverly at yahoo.com Wed Jan 6 08:29:49 2010 From: jamie.beverly at yahoo.com (Jamie Beverly) Date: Tue, 5 Jan 2010 13:29:49 -0800 (PST) Subject: OpenSSH daemon security bug? In-Reply-To: References: <201001051501.11753.davi@leals.com> <531e3e4c1001050721h56f5b5f0n28a1aea6fa9f4dd6@mail.gmail.com> <4B437606.9020601@fifthhorseman.net> <887124.18116.qm@web31809.mail.mud.yahoo.com> Message-ID: <346374.24884.qm@web31815.mail.mud.yahoo.com> ----- Original Message ---- > From: Michael Stone > So you're commonly seeing network-based brute-force (not dictionary) attacks > against passwords via SSH? That's the brute-force risk that you're mitigating by > going to private keys. In my experience the password compromises via SSH are for > passwords like "admin" and "password"--which you can mitigate perfectly well > without going to key based auth. If you're really curious about this you can gin > up a ssh that logs incoming passwords and see what people are actually trying. Yes, in fact brute-force ssh-scans do occur quite frequently. Granted, they are not as frequent as dictionary scans. However, because even "strong" passwords/phrases typically contain less than 40 bits of entropy, the time it takes to brute-force even "strong" passwords/phrases is finite, and even comparatively brief. > (The other major vector is password capture at a compromised endpoint, but keys > in themselves aren't going to help you there.) Actually, keys themselves are going to help you there; because public key authentication relies on the use of a randomly generated challenge response; an intruder cannot steal your private key from an end-point you are connecting to. They can only determine that you have that private key. They could easily harvest passwords, as the passwords themselves, or a hash of them (as with some SASL mechanisms), must be transmitted to the host with password authentication. A password is the authentication token itself, A private key is used to demonstrate the ability to decipher a challenge, and that correctly-deciphered challenge is the authentication token, which is used only once. > I don't think anyone can argue > that there aren't issues with SSH passwords being attacked these days, but those > are pretty much always "stupid password" issues rather than "password" issues. True. > One way to mitigate that is to make a bunch of configuration changes to disallow > passwords. Another way to mitigate that is to make a bunch of configuration > changes to disallow stupid passwords. In general the problem tends to be that > someone didn't configure things up front to disallow stupid passwords and then > blamed the technology rather than the configuration. Except that passwords as a form of authentication are themselves fundamentally weak. Humans are simply not generally capable of remembering strings containing hundreds of bits of entropy. Even strong passwords take only days to weeks to brute-force verses the years to decades it would take to brute-force a private key. > > As dkg points out, you have to trust your users not to do stupid things, and > more often than not, continue to find yourself disappointed when they do in > fact, do stupid things. That said, there is NO case where password > authentication is MORE, or even nearly AS secure as private key authentication. > Public key authentication mitigates more risk.Period. > > Spoken as someone who hasn't seen massive network compromises caused by poorly > managed SSH keys? Spoken as someone who has seen several massive network compromises caused by cracked "strong" passwords, containing >= 10 characters, 2 special, 3 upper, and 3 lower, required to be changed every 30 days, and no sooner than 10 days after having been changed, and not allowed to be duplicated for at least 10 iterations... But you're right, I have not seen a massive network compromise caused by poorly managed SSH keys, though I don't discount that it certainly could happen. > Public keys mitigate *different* risks and have *different* > vulnerabilities. Essentially true, public keys mitigate the most common attack methods used for authentication based intrusions: brute-force/dictionary based attacks, and end-point/in-transit token interception/theft. Passwords do not. Both require the the individual to prove they have a valid authentication token. > Making any kind of general pronouncement is dangerously > oversimplifying things. Here is a general pronouncement that is not oversimplifying things: Passwords: Have no password protecting them from theft. Strong passwords contain around 36-100 bits of entropy Are crackable via brute-force within days to weeks Even "strong" passwords are likely crackable more rapidly via 'hybrid' dictionary attacks. Must be sent to a remote end-point to authenticate, and so could easily be stolen. Can be sent via email, or stored in clear-text in files, etc. Private keys: May or may not have a password protecting them from theft. The worst possible private key contains 768 bits of entropy Crackable via brute-force only, and that would in theory take years to decades. Are never sent to a remote end-point for authentication. Can be sent via email, or stored in clear-text in files, etc. The apples-to-apples comparison clearly demonstrates that public key authentication imparts less risk (at least until somebody comes up with a good P=NP proof...). And also clearly demonstrates that people can still do stupid things with them, because people can do stupid things with anything, even biometrics. > Put a different way: given your particular threat profile, is a 2048 bit key > less secure than a 4096 bit key? Would a 8192 bit key add a lot to your > security? Sometimes security has nothing to do with the math and a lot more to > do with the implementation and the human factors. A 2048 bit key is less secure than a 4096 bit key, but as both would take years to crack (the latter taking far longer) the difference in practical security is less relevant. Security is always about math: Two fields of math in particular: probability, and accounting; aka How likely is this threat, and how much $$$ would it cost us if exploited. The human factors lend only to probability, and are most easily addressed by education, and making insecure practices less convenient. > Mike Stone From davi at leals.com Wed Jan 6 09:43:02 2010 From: davi at leals.com (Davi Diaz) Date: Tue, 5 Jan 2010 22:43:02 +0000 Subject: OpenSSH daemon security bug? In-Reply-To: <49ba991c-fa0c-11de-9b6a-001cc0cda50c@msgid.mathom.us> References: <201001051501.11753.davi@leals.com> <49ba991c-fa0c-11de-9b6a-001cc0cda50c@msgid.mathom.us> Message-ID: <201001052243.03303.davi@leals.com> Michael Stone wrote: > It's true that for some threats a poorly managed ssh private key > is weaker authentication than a well managed password. > > Trying to fix poor password management (brute force ssh password > guessing doesn't work with well managed password policies) by > mandating the use of ssh keys is generally a recipe for disaster. [...] > In many cases the ideal option would be *both* a certificate *and* > a password. That is to say, a private key protected by password and password-access disabled via "PasswordAuthentication no". Unfortunately, as you wrote, we can not even check if the private key is being protected by a password, however we can check that a password account is strong. Unfortunately we can not configure sshd to require both account-password and key authentication to be able to login. That maybe would help to solve the key management risk because at least we could automate the check to force the use of strong account-passwords in our policy security. From davi at leals.com Wed Jan 6 09:47:02 2010 From: davi at leals.com (Davi Diaz) Date: Tue, 5 Jan 2010 22:47:02 +0000 Subject: OpenSSH daemon security bug? In-Reply-To: <4B4377C5.2060006@fifthhorseman.net> References: <201001051501.11753.davi@leals.com> <201001051713.42255.davi@leals.com> <4B4377C5.2060006@fifthhorseman.net> Message-ID: <201001052247.02478.davi@leals.com> Daniel Kahn Gillmor wrote: > > OK, If all users agree about following the security policy I would be in > > favour to allow ssh-key access, blocking the password one by being less > > secure. > > Recent versions of OpenSSH allow you to set your decisions about who > gets key-based (or password-based) access on a more fine-grained level. > For more info, see the Match keyword in sshd_config(8). Thank you very much! I am going to read the manual of the last OpenSSH version. From davi at leals.com Wed Jan 6 10:08:04 2010 From: davi at leals.com (Davi Diaz) Date: Tue, 5 Jan 2010 23:08:04 +0000 Subject: OpenSSH daemon security bug? In-Reply-To: <887124.18116.qm@web31809.mail.mud.yahoo.com> References: <201001051501.11753.davi@leals.com> <4B437606.9020601@fifthhorseman.net> <887124.18116.qm@web31809.mail.mud.yahoo.com> Message-ID: <201001052308.04647.davi@leals.com> The below one has been a good technical analysis taking into account the human factor too. Thanks! Jamie Beverly wrote: > ----- Original Message ---- > > > From: Daniel Kahn Gillmor > > To: openssh-unix-dev at mindrot.org > > Sent: Tue, January 5, 2010 9:25:26 AM > > Subject: Re: OpenSSH daemon security bug? > > > > On 01/05/2010 10:21 AM, Mark Janssen wrote: > > > On Tue, Jan 5, 2010 at 4:01 PM, Davi Diaz wrote: > > >> co-worker wrote: > > >>> I am all for encouraging key-based logins, but I think disabling > > >>> password logins completely actually reduces security. > > > > > > I must agree here, while keys are better then passwords, it's > > > impossible to enforce passphrase quality on keys, while it is possible > > > to enforce some quality on passwords. > > > > i don't think you're comparing the same thing, though. You can make > > sure it's a really really strong password, but it's still *not* possible > > to enforce that your users keep their password safe. > > > > If you're worried that your users might leave an unprotected key lying > > around, you should *also* be worried that those same users might send > > their password via e-mail (even if it's just "to themselves as a > > reminder"), or write it in a cleartext file on their computer, reuse it > > for their amazon account, for their blog, etc. > > > > At some level, you have to trust your users if they're going to use your > > system. And have good backups, easy recovery, and regular user > > education about good practices, of course ;) > > > > --dkg > > More to the point, password authentication is fundamentally less secure > than ssh public key authentication. The comparisons being made here are > seemingly getting hung up on the word "password", and supposing that the > weakness of one password is the same as the weakness of another. They are > not. > > The password on a private key helps to protect that private key from being > stolen. However, the most often exploited, and hence far greater risks are > brute-force/dictionary attacks and password interception. > > Even a passwordless private key has between 768-4096 bits of entropy, which > is roughly equivalent to a human-remembered passphrase of between > 2000-10,000 characters. Good luck enforcing that password policy! > > And as to interception, private key exchanges never actually send the > private key on the wire, instead they rely on a challenge/response using a > randomly generated number. (essentially a packet containing a random number > is encrypted to a public key, and that encrypted message is sent as the > challenge; the client is then forced to decrypt that message to prove that > it can decipher that number; if it helps by way of analogy, its like > sending somebody a GPG encrypted email and then having them prove they have > the key that can decrypt it) > > As dkg points out, you have to trust your users not to do stupid things, > and more often than not, continue to find yourself disappointed when they > do in fact, do stupid things. That said, there is NO case where password > authentication is MORE, or even nearly AS secure as private key > authentication. Public key authentication mitigates more risk. Period. From mstone at mathom.us Wed Jan 6 07:31:34 2010 From: mstone at mathom.us (Michael Stone) Date: Tue, 05 Jan 2010 15:31:34 -0500 Subject: OpenSSH daemon security bug? In-Reply-To: <887124.18116.qm@web31809.mail.mud.yahoo.com> References: <201001051501.11753.davi@leals.com> <531e3e4c1001050721h56f5b5f0n28a1aea6fa9f4dd6@mail.gmail.com> <4B437606.9020601@fifthhorseman.net> <887124.18116.qm@web31809.mail.mud.yahoo.com> Message-ID: On Tue, Jan 05, 2010 at 11:06:28AM -0800, Jamie Beverly wrote: >More to the point, password authentication is fundamentally less secure than ssh public key authentication. The comparisons being made here are seemingly getting hung up on the word "password", and supposing that the weakness of one password is the same as the weakness of another. They are not. > >The password on a private key helps to protect that private key from being stolen. However, the most often exploited, and hence far greater risks are brute-force/dictionary attacks and password interception. So you're commonly seeing network-based brute-force (not dictionary) attacks against passwords via SSH? That's the brute-force risk that you're mitigating by going to private keys. In my experience the password compromises via SSH are for passwords like "admin" and "password"--which you can mitigate perfectly well without going to key based auth. If you're really curious about this you can gin up a ssh that logs incoming passwords and see what people are actually trying. (The other major vector is password capture at a compromised endpoint, but keys in themselves aren't going to help you there.) I don't think anyone can argue that there aren't issues with SSH passwords being attacked these days, but those are pretty much always "stupid password" issues rather than "password" issues. One way to mitigate that is to make a bunch of configuration changes to disallow passwords. Another way to mitigate that is to make a bunch of configuration changes to disallow stupid passwords. In general the problem tends to be that someone didn't configure things up front to disallow stupid passwords and then blamed the technology rather than the configuration. >As dkg points out, you have to trust your users not to do stupid things, and more often than not, continue to find yourself disappointed when they do in fact, do stupid things. That said, there is NO case where password authentication is MORE, or even nearly AS secure as private key authentication. Public key authentication mitigates more risk.Period. Spoken as someone who hasn't seen massive network compromises caused by poorly managed SSH keys? Public keys mitigate *different* risks and have *different* vulnerabilities. Making any kind of general pronouncement is dangerously oversimplifying things. Put a different way: given your particular threat profile, is a 2048 bit key less secure than a 4096 bit key? Would a 8192 bit key add a lot to your security? Sometimes security has nothing to do with the math and a lot more to do with the implementation and the human factors. Mike Stone From djm at mindrot.org Wed Jan 6 10:34:09 2010 From: djm at mindrot.org (Damien Miller) Date: Wed, 6 Jan 2010 10:34:09 +1100 (EST) Subject: OpenSSH daemon security bug? In-Reply-To: <201001052243.03303.davi@leals.com> References: <201001051501.11753.davi@leals.com> <49ba991c-fa0c-11de-9b6a-001cc0cda50c@msgid.mathom.us> <201001052243.03303.davi@leals.com> Message-ID: On Tue, 5 Jan 2010, Davi Diaz wrote: > Unfortunately we can not configure sshd to require both > account-password and key authentication to be able to login. That > maybe would help to solve the key management risk because at least we > could automate the check to force the use of strong account-passwords > in our policy security. Watch activity on https://bugzilla.mindrot.org/show_bug.cgi?id=983 if you are interested in progress on this feature. -d From jamie.beverly at yahoo.com Wed Jan 6 09:36:28 2010 From: jamie.beverly at yahoo.com (Jamie Beverly) Date: Tue, 5 Jan 2010 14:36:28 -0800 (PST) Subject: OpenSSH daemon security bug? In-Reply-To: <04d07070-fa2e-11de-9b6a-001cc0cda50c@msgid.mathom.us> References: <201001051501.11753.davi@leals.com> <531e3e4c1001050721h56f5b5f0n28a1aea6fa9f4dd6@mail.gmail.com> <201001051713.42255.davi@leals.com> <20100105182700.GA23830@zzlevo.net> <04d07070-fa2e-11de-9b6a-001cc0cda50c@msgid.mathom.us> Message-ID: <78406.28250.qm@web31814.mail.mud.yahoo.com> Full View Re: OpenSSH daemon security bug?... From: Jamie Beverly ...Add to Contacts To: Michael Stone ________________________________ ----- Original Message ---- > From: Michael Stone > To: openssh-unix-dev at mindrot.org > Sent: Tue, January 5, 2010 11:18:03 AM > Subject: Re: OpenSSH daemon security bug? > > On Tue, Jan 05, 2010 at 07:27:00PM +0100, you wrote: > > Password authentication with a weak password is in itself no more secure > > than public key authentication where the private key is protected with a > > weak password. In the former case the attacker only needs to guess the > > password, in the latter case they also need to get the private key. > > Knowledge of the public key does not help an attacker. > > You can force the password complexity on the server side. You can't force the > key management through technical means. That's a very important difference. > Where this has proven to be a major headache in practice is when people have > copies of their private keys scattered around on various machines, leading to > intruders being able to jump from one machine to another. (Possibly from one > organization to another, especially in collaborative environments like .edu's.) > Private keys are seldom expired on a routine basis, so there may be forgotten > copies lying about that haven't been used in years. (Should people do this? No. > Does it happen? Yes. Can you prove none of your users have done it? Probably > not.) Even with a strong password, there's a big difference between a passive > offline attack against a key and an active brute force of a login password. This could easily be handled via a PAM module; one that tracks user's public keys, and forces them to change every N (configurable) days... with similar rules to password management, i.e. no repeats within N days, cannot change for N days, etc,etc... I suspect it would have to sit in the session stack so that it would still be hit in the pam_open_session phase of login via public-key authentication... If there is interest, I'd be happy to write it. > Mike Stone > _______________________________________________ > openssh-unix-dev mailing list > openssh-unix-dev at mindrot.org > https://lists.mindrot.org/mailman/listinfo/openssh-unix-dev From mstone at mathom.us Wed Jan 6 07:37:40 2010 From: mstone at mathom.us (Michael Stone) Date: Tue, 05 Jan 2010 15:37:40 -0500 Subject: OpenSSH daemon security bug? In-Reply-To: <4B43920F.7030502@noaa.gov> References: <201001051501.11753.davi@leals.com> <531e3e4c1001050721h56f5b5f0n28a1aea6fa9f4dd6@mail.gmail.com> <4B437606.9020601@fifthhorseman.net> <904050dc-fa2c-11de-9b6a-001cc0cda50c@msgid.mathom.us> <4B43920F.7030502@noaa.gov> Message-ID: <59e4923e-fa39-11de-9b6a-001cc0cda50c@msgid.mathom.us> On Tue, Jan 05, 2010 at 07:25:03PM +0000, Jefferson Ogata wrote: >For what it's worth, as an incident handler, I've witnessed a lot of >cases of password guessing against sshd in my days. I haven't seen a >single instance of someone stealing a passphrased pubkey and using that, Consider yourself lucky. :-) Next question: how strong were the guessed passwords? (Rhetorical; you note later down that they were generally the result of someone doing something dumb. Note that the facilities to centralize and enforce password policy are fairly common--how do you prevent that same admin from doing something dumb with the key "just temporarily"?) >let alone discovering the passphrase on a key; the only compromises I've >seen that involve pubkeys are intruders using an unpassphrased key from >the system on which it resides to get to a related system, generally by >consulting .ssh/known_hosts. Yup, that would be it. >Of course, attacks against pubkeys are >possible, but they almost never happen. *OF COURSE* nobody is attacking the keys cryptographically, that's not the weak link. Mike Stone From Jefferson.Ogata at noaa.gov Wed Jan 6 10:51:57 2010 From: Jefferson.Ogata at noaa.gov (Jefferson Ogata) Date: Tue, 05 Jan 2010 23:51:57 +0000 Subject: OpenSSH daemon security bug? In-Reply-To: <59e4923e-fa39-11de-9b6a-001cc0cda50c@msgid.mathom.us> References: <201001051501.11753.davi@leals.com> <531e3e4c1001050721h56f5b5f0n28a1aea6fa9f4dd6@mail.gmail.com> <4B437606.9020601@fifthhorseman.net> <904050dc-fa2c-11de-9b6a-001cc0cda50c@msgid.mathom.us> <4B43920F.7030502@noaa.gov> <59e4923e-fa39-11de-9b6a-001cc0cda50c@msgid.mathom.us> Message-ID: <4B43D09D.2080307@noaa.gov> On 2010-01-05 20:37, Michael Stone wrote: > On Tue, Jan 05, 2010 at 07:25:03PM +0000, Jefferson Ogata wrote: >> For what it's worth, as an incident handler, I've witnessed a lot of >> cases of password guessing against sshd in my days. I haven't seen a >> single instance of someone stealing a passphrased pubkey and using that, > > Consider yourself lucky. :-) Next question: how strong were the guessed > passwords? (Rhetorical; you note later down that they were generally the > result of someone doing something dumb. Note that the facilities to > centralize and enforce password policy are fairly common--how do you > prevent that same admin from doing something dumb with the key "just > temporarily"?) I'm not lucky. If you've seen someone steal a key *and* a passphrase and use it, you're the lucky/unlucky one. I've been doing incident response for over 10 years and I've never seen that happen. Of course admins can be dumb in many ways, but they're far more likely in practice to be dumb in the way of assigning a weak password while creating a role account, than in the way of creating a passphraseless keypair for a role account and somehow sharing the private key. >> let alone discovering the passphrase on a key; the only compromises I've >> seen that involve pubkeys are intruders using an unpassphrased key from >> the system on which it resides to get to a related system, generally by >> consulting .ssh/known_hosts. > > Yup, that would be it. > >> Of course, attacks against pubkeys are >> possible, but they almost never happen. > > *OF COURSE* nobody is attacking the keys cryptographically, that's not > the weak link. I'm not talking about cryptographic attacks; I'm talking about attacks against passphrase protection on keys, e.g. keyloggers. It's possible, but it's not a problem people are having to deal with on a frequent basis. -- Jefferson Ogata NOAA Computer Incident Response Team (N-CIRT) "Never try to retrieve anything from a bear."--National Park Service From Jefferson.Ogata at noaa.gov Wed Jan 6 10:56:05 2010 From: Jefferson.Ogata at noaa.gov (Jefferson Ogata) Date: Tue, 05 Jan 2010 23:56:05 +0000 Subject: OpenSSH daemon security bug? In-Reply-To: <20100105201909.GA11258@finestructure.net> References: <201001051501.11753.davi@leals.com> <531e3e4c1001050721h56f5b5f0n28a1aea6fa9f4dd6@mail.gmail.com> <4B43897B.3050606@noaa.gov> <20100105201909.GA11258@finestructure.net> Message-ID: <4B43D195.7010505@noaa.gov> On 2010-01-05 20:19, Jameson Rollins wrote: > On Tue, Jan 05, 2010 at 06:48:27PM +0000, Jefferson Ogata wrote: >> Actually the server could theoretically determine heuristically if the >> key has no passphrase (or if the user is using ssh-agent) by timing the >> key transaction. I've often thought it would be useful for sshd to have >> an option for requiring that there be a delay before each pubkey >> transaction for the purpose of assuring that a passphrase is being typed >> on the client side. > > Actually most agents cache the key in memory, and most don't require > passwords to be typed in for every use, so I don't think this would > work. AFAIK the passphrase isn't prompted until initial authentication method negotiation has occurred and the client and server have agreed on a keypair to try. So if a pubkey response is received within, say, 200 ms after auth methods have been negotiated, sshd should be able to conclude that the key either lacks a passphrase or that the user is running ssh-agent. For me this would be a useful feature. -- Jefferson Ogata NOAA Computer Incident Response Team (N-CIRT) "Never try to retrieve anything from a bear."--National Park Service From dtucker at zip.com.au Wed Jan 6 10:14:38 2010 From: dtucker at zip.com.au (Darren Tucker) Date: Wed, 06 Jan 2010 10:14:38 +1100 Subject: OpenSSH daemon security bug? In-Reply-To: <201001052243.03303.davi@leals.com> References: <201001051501.11753.davi@leals.com> <49ba991c-fa0c-11de-9b6a-001cc0cda50c@msgid.mathom.us> <201001052243.03303.davi@leals.com> Message-ID: <4B43C7DE.2030207@zip.com.au> Davi Diaz wrote: > Unfortunately we can not configure sshd to require both account-password and > key authentication to be able to login. That maybe would help to solve the > key management risk because at least we could automate the check to force the > use of strong account-passwords in our policy security. There's an enhancement request for this (which Damien and I plan to take a look at next week). https://bugzilla.mindrot.org/show_bug.cgi?id=983 -- Darren Tucker (dtucker at zip.com.au) GPG key 8FF4FA69 / D9A3 86E9 7EEE AF4B B2D4 37C9 C982 80C7 8FF4 FA69 Good judgement comes with experience. Unfortunately, the experience usually comes from bad judgement. From jamie.beverly at yahoo.com Wed Jan 6 11:08:57 2010 From: jamie.beverly at yahoo.com (Jamie Beverly) Date: Tue, 5 Jan 2010 16:08:57 -0800 (PST) Subject: OpenSSH daemon security bug? In-Reply-To: <78406.28250.qm@web31814.mail.mud.yahoo.com> References: <201001051501.11753.davi@leals.com> <531e3e4c1001050721h56f5b5f0n28a1aea6fa9f4dd6@mail.gmail.com> <201001051713.42255.davi@leals.com> <20100105182700.GA23830@zzlevo.net> <04d07070-fa2e-11de-9b6a-001cc0cda50c@msgid.mathom.us> <78406.28250.qm@web31814.mail.mud.yahoo.com> Message-ID: <550769.96054.qm@web31802.mail.mud.yahoo.com> > ----- Original Message ---- > > > From: Michael Stone > > To: openssh-unix-dev at mindrot.org > > Sent: Tue, January 5, 2010 11:18:03 AM > > Subject: Re: OpenSSH daemon security bug? > > > > On Tue, Jan 05, 2010 at 07:27:00PM +0100, you wrote: > > > Password authentication with a weak password is in itself no more secure > > > than public key authentication where the private key is protected with a > > > weak password. In the former case the attacker only needs to guess the > > > password, in the latter case they also need to get the private key. > > > Knowledge of the public key does not help an attacker. > > > > You can force the password complexity on the server side. You can't force the > > key management through technical means. That's a very important difference. > > Where this has proven to be a major headache in practice is when people have > > copies of their private keys scattered around on various machines, leading to > > intruders being able to jump from one machine to another. (Possibly from one > > organization to another, especially in collaborative environments like > .edu's.) > > Private keys are seldom expired on a routine basis, so there may be forgotten > > copies lying about that haven't been used in years. (Should people do this? > No. > > Does it happen? Yes. Can you prove none of your users have done it? Probably > > not.) Even with a strong password, there's a big difference between a passive > > offline attack against a key and an active brute force of a login password. > > > This > could easily be handled via a PAM module; one that tracks user's public > keys, and forces them to change every N (configurable) days... with > similar rules to password management, i.e. no repeats within N days, > cannot change for N days, etc,etc... > > I suspect it would have to > sit in the session stack so that it would still be hit in the > pam_open_session phase of login via public-key authentication... > > If there is interest, I'd be happy to write it. > Actually thinking more about it, this might make more sense as an sshd configuration item. Especially since it would mingle very nicely with Match rules. I'm thinking something like, when a user logs in who is required to pass key-aging rules, sshd would check the mtime of the configured authorized_keys file as preliminary check, and if the file itself is recent enough, it would then then iterates the users public keys. If they key has already been recorded in an expiration cache, it would compare the current-time against the recorded-time; if they key has not already been recorded, the key would get recorded with a timestamp (the mtime of the file? current time? something...). Any public key known to not match the configured aging rules would simply get ignored. Additionally, a host-wide black-listing facility would be good. There would be corner cases that this wouldn't catch, e.g. a user who had never logged in before, but had a recently touched authorized_keys file containing old keys; but in the general case, it might be worthwhile to have... Thoughts? Additions? Alternate approaches? From carson at taltos.org Wed Jan 6 12:19:37 2010 From: carson at taltos.org (Carson Gaspar) Date: Tue, 05 Jan 2010 17:19:37 -0800 Subject: OpenSSH daemon security bug? In-Reply-To: <04d07070-fa2e-11de-9b6a-001cc0cda50c@msgid.mathom.us> References: <201001051501.11753.davi@leals.com> <531e3e4c1001050721h56f5b5f0n28a1aea6fa9f4dd6@mail.gmail.com> <201001051713.42255.davi@leals.com> <20100105182700.GA23830@zzlevo.net> <04d07070-fa2e-11de-9b6a-001cc0cda50c@msgid.mathom.us> Message-ID: <4B43E529.8030902@taltos.org> Michael Stone wrote: > Private keys are seldom expired on a routine basis, so there may be > forgotten copies lying about that haven't been used in years. (Should > people do this? No. Does it happen? Yes. Can you prove none of your > users have done it? Probably not.) Even with a strong password, > there's a big difference between a passive offline attack against a > key and an active brute force of a login password. You _can_ enforce private key expiry, if you wish, but it requires some extra code. One possible implementation: - Use an authorized_keys path outside the user's control (sshd already allows this) - Provide a method for users to add a new public key (via HTTP, command line with setuid / sudo, ...). Said method needs to record the date the key was added (in a comment or in a sidecar DB). It should also remember past keys (easy to implement via commented out old keys or in a sidecar DB) - Create a pam_session module to warn users of approaching key expiry on login (or add something to the standard shell profiles, but PAM is more robust if you're using it already). - Expire the keys via a daemon / scheduled job, optionally changing a sufficiently recently expired key to have a forced command that allows a user to update their key, and nothing else. - (Optional) synchronize authorized keys across a set of servers in the same security domain for ease of use of users (as their home dir no longer provides a de facto synchronization method) Then you have the mechanism to implement a policy (key length / type, max age, etc.) -- Carson Gaspar From carson at taltos.org Wed Jan 6 12:22:16 2010 From: carson at taltos.org (Carson Gaspar) Date: Tue, 05 Jan 2010 17:22:16 -0800 Subject: OpenSSH daemon security bug? In-Reply-To: <550769.96054.qm@web31802.mail.mud.yahoo.com> References: <201001051501.11753.davi@leals.com> <531e3e4c1001050721h56f5b5f0n28a1aea6fa9f4dd6@mail.gmail.com> <201001051713.42255.davi@leals.com> <20100105182700.GA23830@zzlevo.net> <04d07070-fa2e-11de-9b6a-001cc0cda50c@msgid.mathom.us> <78406.28250.qm@web31814.mail.mud.yahoo.com> <550769.96054.qm@web31802.mail.mud.yahoo.com> Message-ID: <4B43E5C8.2090209@taltos.org> Jamie Beverly wrote: >> This could easily be handled via a PAM module; one that tracks >> user's public keys, and forces them to change every N >> (configurable) days... with similar rules to password management, >> i.e. no repeats within N days, cannot change for N days, etc,etc... >> >> >> I suspect it would have to sit in the session stack so that it >> would still be hit in the pam_open_session phase of login via >> public-key authentication... >> >> If there is interest, I'd be happy to write it. >> > > > Actually thinking more about it, this might make more sense as an > sshd configuration item. Especially since it would mingle very nicely > with Match rules. > > I'm thinking something like, when a user logs in who is required to > pass key-aging rules, sshd would check the mtime of the configured > authorized_keys file as preliminary check, and if the file itself is > recent enough, it would then then iterates the users public keys. If > they key has already been recorded in an expiration cache, it would > compare the current-time against the recorded-time; if they key has > not already been recorded, the key would get recorded with a > timestamp (the mtime of the file? current time? something...). Any > public key known to not match the configured aging rules would simply > get ignored. Additionally, a host-wide black-listing facility would > be good. > > There would be corner cases that this wouldn't catch, e.g. a user who > had never logged in before, but had a recently touched > authorized_keys file containing old keys; but in the general case, it > might be worthwhile to have... > > Thoughts? Additions? Alternate approaches? See the mail I just sent to the list. Leaving authorized_keys in a user's home directory is a really bad idea if you want to actually enforce policy. -- Carson Gaspar From jamie.beverly at yahoo.com Wed Jan 6 13:46:24 2010 From: jamie.beverly at yahoo.com (Jamie Beverly) Date: Tue, 5 Jan 2010 18:46:24 -0800 (PST) Subject: OpenSSH daemon security bug? In-Reply-To: <4B43E5C8.2090209@taltos.org> References: <201001051501.11753.davi@leals.com> <531e3e4c1001050721h56f5b5f0n28a1aea6fa9f4dd6@mail.gmail.com> <201001051713.42255.davi@leals.com> <20100105182700.GA23830@zzlevo.net> <04d07070-fa2e-11de-9b6a-001cc0cda50c@msgid.mathom.us> <78406.28250.qm@web31814.mail.mud.yahoo.com> <550769.96054.qm@web31802.mail.mud.yahoo.com> <4B43E5C8.2090209@taltos.org> Message-ID: <397938.91920.qm@web31813.mail.mud.yahoo.com> ----- Original Message ---- > From: Carson Gaspar > To: "" > Sent: Tue, January 5, 2010 5:19:37 PM > Subject: Re: OpenSSH daemon security bug? > > Michael Stone wrote: > > > Private keys are seldom expired on a routine basis, so there may be > > forgotten copies lying about that haven't been used in years. (Should > > people do this? No. Does it happen? Yes. Can you prove none of your > > users have done it? Probably not.) Even with a strong password, > > there's a big difference between a passive offline attack against a > > key and an active brute force of a login password. > > You _can_ enforce private key expiry, if you wish, but it requires some extra > code. One possible implementation: > > - Use an authorized_keys path outside the user's control (sshd already allows > this) > > - Provide a method for users to add a new public key (via HTTP, command line > with setuid / sudo, ...). Said method needs to record the date the key was added > (in a comment or in a sidecar DB). It should also remember past keys (easy to > implement via commented out old keys or in a sidecar DB) > > - Create a pam_session module to warn users of approaching key expiry on login > (or add something to the standard shell profiles, but PAM is more robust if > you're using it already). > > - Expire the keys via a daemon / scheduled job, optionally changing a > sufficiently recently expired key to have a forced command that allows a user to > update their key, and nothing else. > > - (Optional) synchronize authorized keys across a set of servers in the same > security domain for ease of use of users (as their home dir no longer provides a > de facto synchronization method) > > Then you have the mechanism to implement a policy (key length / type, max age, > etc.) > > -- Carson Gaspar That is almost precisely how I've always done it whenever I was concerned about users following key policies, minus the pam_session module (I was a bit lazier than that, and just used an /etc/profile.d script, as you noted as an alternative) I proposed a possible feature request in another message within this thread to add some level of daemon-level configuration and enforcing of aging policy; but I've not entirely convinced myself its really necessary given how easily it can be done outside of openssh... (my perl script for expiring users' keys is I think 68 lines ish... ) From mstone at mathom.us Wed Jan 6 11:56:09 2010 From: mstone at mathom.us (Michael Stone) Date: Tue, 05 Jan 2010 19:56:09 -0500 Subject: OpenSSH daemon security bug? In-Reply-To: <346374.24884.qm@web31815.mail.mud.yahoo.com> References: <201001051501.11753.davi@leals.com> <531e3e4c1001050721h56f5b5f0n28a1aea6fa9f4dd6@mail.gmail.com> <4B437606.9020601@fifthhorseman.net> <887124.18116.qm@web31809.mail.mud.yahoo.com> <346374.24884.qm@web31815.mail.mud.yahoo.com> Message-ID: On Tue, Jan 05, 2010 at 01:29:49PM -0800, Jamie Beverly wrote: >Yes, in fact brute-force ssh-scans do occur quite frequently. Granted, >they are not as frequent as dictionary scans. However, because even >"strong" passwords/phrases typically contain less than 40 bits of >entropy, the time it takes to brute-force even "strong" >passwords/phrases is finite, and even comparatively brief. So you don't rate limit attempts or cap failures? Interesting. Mike Stone From pgsery at swcp.com Wed Jan 6 15:35:29 2010 From: pgsery at swcp.com (Paul) Date: Tue, 05 Jan 2010 21:35:29 -0700 Subject: OpenSSH daemon security bug? In-Reply-To: References: <201001051501.11753.davi@leals.com> <49ba991c-fa0c-11de-9b6a-001cc0cda50c@msgid.mathom.us> <201001052243.03303.davi@leals.com> Message-ID: <4B441311.2040209@swcp.com> You might want to check out pamobc at http://sourceforge.net/projects/pamobc. pamobc provides sshd with an out-of-band challenge-response authentication mechanism delivers one-time passwords through any user-land mechanism of your choice (i.e., email, pager, cell phone, etc). The one-time pwds can optionally be encrypted to create a two-factor authentication mechanism. Damien Miller wrote: > On Tue, 5 Jan 2010, Davi Diaz wrote: > > >> Unfortunately we can not configure sshd to require both >> account-password and key authentication to be able to login. That >> maybe would help to solve the key management risk because at least we >> could automate the check to force the use of strong account-passwords >> in our policy security. >> > > Watch activity on https://bugzilla.mindrot.org/show_bug.cgi?id=983 if > you are interested in progress on this feature. > > -d > _______________________________________________ > openssh-unix-dev mailing list > openssh-unix-dev at mindrot.org > https://lists.mindrot.org/mailman/listinfo/openssh-unix-dev > From Jefferson.Ogata at noaa.gov Wed Jan 6 16:47:17 2010 From: Jefferson.Ogata at noaa.gov (Jefferson Ogata) Date: Wed, 06 Jan 2010 05:47:17 +0000 Subject: OpenSSH daemon security bug? In-Reply-To: References: <201001051501.11753.davi@leals.com> <531e3e4c1001050721h56f5b5f0n28a1aea6fa9f4dd6@mail.gmail.com> <4B437606.9020601@fifthhorseman.net> <887124.18116.qm@web31809.mail.mud.yahoo.com> <346374.24884.qm@web31815.mail.mud.yahoo.com> Message-ID: <4B4423E5.3010809@noaa.gov> On 2010-01-06 00:56, Michael Stone wrote: > On Tue, Jan 05, 2010 at 01:29:49PM -0800, Jamie Beverly wrote: >> Yes, in fact brute-force ssh-scans do occur quite frequently. Granted, >> they are not as frequent as dictionary scans. However, because even >> "strong" passwords/phrases typically contain less than 40 bits of >> entropy, the time it takes to brute-force even "strong" >> passwords/phrases is finite, and even comparatively brief. > > So you don't rate limit attempts or cap failures? Interesting. I'm curious, since you bring it up, what method do you prefer for rate limiting failed password attempts and failed pubkey attempts? How well does your method work on routers and other ssh-capable network devices? Also, are you suggesting that Jamie's statement is untrue? -- Jefferson Ogata NOAA Computer Incident Response Team (N-CIRT) "Never try to retrieve anything from a bear."--National Park Service From martin at paljak.pri.ee Wed Jan 6 19:59:04 2010 From: martin at paljak.pri.ee (Martin Paljak) Date: Wed, 6 Jan 2010 10:59:04 +0200 Subject: smart cards (was: OpenSSH daemon security bug?) In-Reply-To: References: Message-ID: On 06.01.2010, at 5:46, openssh-unix-dev-request at mindrot.org wrote: > OpenSSH daemon security bug? If you find find passwords and/or password protected keys not secure I would suggest using private keys on a smart card. There's a bug(with patches) related to smart cards: https://bugzilla.mindrot.org/show_bug.cgi?id=1371 I don't think that guessing about the protection of the private keys would make any sense. You can only be sure if you know that the private part of a keypair is well protected. Hints from the client can't be trusted either. PKCS#11 is a well known, mature interface for interacting with cryptographic objects, there has been a patch for OpenSSH out there for years but no interest whatsoever to integrate it. Instead, OpenSSH directly links in an incomplete way against libopensc (OpenSC). OpenSC does not encourage linking against libopensc unless there is a reason to do it, which OpenSSH does not seem to have. It also limits OpenSSH smartcard support to only the set of cards supported by OpenSC (there are more PKCS#11 libraries out there) Martin, OpenSC dev. -- Martin Paljak http://martin.paljak.pri.ee +372.515.6495 From davi at leals.com Wed Jan 6 21:01:18 2010 From: davi at leals.com (Davi Diaz) Date: Wed, 6 Jan 2010 10:01:18 +0000 Subject: OpenSSH daemon security bug? In-Reply-To: <49ba991c-fa0c-11de-9b6a-001cc0cda50c@msgid.mathom.us> References: <201001051501.11753.davi@leals.com> <49ba991c-fa0c-11de-9b6a-001cc0cda50c@msgid.mathom.us> Message-ID: <201001061001.18572.davi@leals.com> Michael Stone wrote: > mandating the use of ssh keys is generally a recipe for disaster. Does "ssh -A" or any other parameters copy the private key to a remote host? From davi at leals.com Wed Jan 6 21:14:24 2010 From: davi at leals.com (Davi Diaz) Date: Wed, 6 Jan 2010 10:14:24 +0000 Subject: OpenSSH daemon security bug? In-Reply-To: References: <201001051501.11753.davi@leals.com> <201001052243.03303.davi@leals.com> Message-ID: <201001061014.24521.davi@leals.com> Damien Miller wrote: > Watch activity on https://bugzilla.mindrot.org/show_bug.cgi?id=983 if > you are interested in progress on this feature. That 'RequiredMethods' feature will offer the best security possible, even with bad users, because it would allow N-factor authentication in ssh. From aris.adamantiadis at belnet.be Wed Jan 6 21:21:27 2010 From: aris.adamantiadis at belnet.be (Aris Adamantiadis) Date: Wed, 06 Jan 2010 11:21:27 +0100 Subject: OpenSSH daemon security bug? In-Reply-To: <4B43D09D.2080307@noaa.gov> References: <201001051501.11753.davi@leals.com> <531e3e4c1001050721h56f5b5f0n28a1aea6fa9f4dd6@mail.gmail.com> <4B437606.9020601@fifthhorseman.net> <904050dc-fa2c-11de-9b6a-001cc0cda50c@msgid.mathom.us> <4B43920F.7030502@noaa.gov> <59e4923e-fa39-11de-9b6a-001cc0cda50c@msgid.mathom.us> <4B43D09D.2080307@noaa.gov> Message-ID: <4B446427.1010100@belnet.be> Jefferson Ogata a ?crit : > > I'm not lucky. If you've seen someone steal a key *and* a passphrase and > use it, you're the lucky/unlucky one. I've been doing incident response > for over 10 years and I've never seen that happen. I've got feedback of pentesters actually doing that almost each time they do a pentest and succed. Either they compromise the private keys by stealing the password (keypress sniffer, console sniffer, ...) or by fetching the decrypted key in the user agent. Encrypted key files are a layer of protection but they can't stop a competent intruder who can sit down and wait until you actually use your key. Aris From davi at leals.com Wed Jan 6 21:39:03 2010 From: davi at leals.com (Davi Diaz) Date: Wed, 6 Jan 2010 10:39:03 +0000 Subject: OpenSSH daemon security bug? In-Reply-To: <4B446427.1010100@belnet.be> References: <201001051501.11753.davi@leals.com> <4B43D09D.2080307@noaa.gov> <4B446427.1010100@belnet.be> Message-ID: <201001061039.03580.davi@leals.com> Aris Adamantiadis wrote: > Jefferson Ogata a ?crit : > > I'm not lucky. If you've seen someone steal a key *and* a passphrase and > > use it, you're the lucky/unlucky one. I've been doing incident response > > for over 10 years and I've never seen that happen. > > I've got feedback of pentesters actually doing that almost each time > they do a pentest and succed. Either they compromise the private keys by > stealing the password (keypress sniffer, console sniffer, ...) or by > fetching the decrypted key in the user agent. > > Encrypted key files are a layer of protection but they can't stop a > competent intruder who can sit down and wait until you actually use > your key. However password-account based access can not avoid keypress sniffer, console sniffer, ... neither. From Jefferson.Ogata at noaa.gov Wed Jan 6 22:48:43 2010 From: Jefferson.Ogata at noaa.gov (Jefferson Ogata) Date: Wed, 06 Jan 2010 11:48:43 +0000 Subject: OpenSSH daemon security bug? In-Reply-To: <4B446427.1010100@belnet.be> References: <201001051501.11753.davi@leals.com> <531e3e4c1001050721h56f5b5f0n28a1aea6fa9f4dd6@mail.gmail.com> <4B437606.9020601@fifthhorseman.net> <904050dc-fa2c-11de-9b6a-001cc0cda50c@msgid.mathom.us> <4B43920F.7030502@noaa.gov> <59e4923e-fa39-11de-9b6a-001cc0cda50c@msgid.mathom.us> <4B43D09D.2080307@noaa.gov> <4B446427.1010100@belnet.be> Message-ID: <4B44789B.4030601@noaa.gov> On 2010-01-06 10:21, Aris Adamantiadis wrote: > Jefferson Ogata a ?crit : >> I'm not lucky. If you've seen someone steal a key *and* a passphrase and >> use it, you're the lucky/unlucky one. I've been doing incident response >> for over 10 years and I've never seen that happen. > > I've got feedback of pentesters actually doing that almost each time > they do a pentest and succed. Either they compromise the private keys by > stealing the password (keypress sniffer, console sniffer, ...) or by > fetching the decrypted key in the user agent. Encrypted key files are a > layer of protection but they can't stop a competent intruder who can sit > down and wait until you actually use your key. That is true. But the vast majority of intruders are incompetent. As for your pen-testers, they had to get on the box with the private key somehow before they could perform that attack. And they're pen-testers. Have you ever seen this happen in a genuine intrusion? -- Jefferson Ogata NOAA Computer Incident Response Team (N-CIRT) "Never try to retrieve anything from a bear."--National Park Service From mstone at mathom.us Wed Jan 6 23:50:50 2010 From: mstone at mathom.us (Michael Stone) Date: Wed, 06 Jan 2010 07:50:50 -0500 Subject: OpenSSH daemon security bug? In-Reply-To: <4B43E529.8030902@taltos.org> References: <201001051501.11753.davi@leals.com> <531e3e4c1001050721h56f5b5f0n28a1aea6fa9f4dd6@mail.gmail.com> <201001051713.42255.davi@leals.com> <20100105182700.GA23830@zzlevo.net> <04d07070-fa2e-11de-9b6a-001cc0cda50c@msgid.mathom.us> <4B43E529.8030902@taltos.org> Message-ID: On Tue, Jan 05, 2010 at 05:19:37PM -0800, you wrote: > You _can_ enforce private key expiry, if you wish, You can do all sorts of things, and people are doing those things. But those people have thought out the risks and made sure that they've mitigated them as well as they can in their environment--not just blindy trusted that "keys are better". I'm merely trying to get people thinking, not saying that keys are inherently less secure. Mike Stone From mstone at mathom.us Thu Jan 7 00:07:07 2010 From: mstone at mathom.us (Michael Stone) Date: Wed, 06 Jan 2010 08:07:07 -0500 Subject: OpenSSH daemon security bug? In-Reply-To: <4B44789B.4030601@noaa.gov> References: <201001051501.11753.davi@leals.com> <531e3e4c1001050721h56f5b5f0n28a1aea6fa9f4dd6@mail.gmail.com> <4B437606.9020601@fifthhorseman.net> <904050dc-fa2c-11de-9b6a-001cc0cda50c@msgid.mathom.us> <4B43920F.7030502@noaa.gov> <59e4923e-fa39-11de-9b6a-001cc0cda50c@msgid.mathom.us> <4B43D09D.2080307@noaa.gov> <4B446427.1010100@belnet.be> <4B44789B.4030601@noaa.gov> Message-ID: <20100106130707.GD26761@mathom.us> On Wed, Jan 06, 2010 at 11:48:43AM +0000, Jefferson Ogata wrote: >That is true. But the vast majority of intruders are incompetent. So are the vast majority of pen-testers. :-) Sturgeon's Revelation. It only takes one competent one to spread the word, then you have a major outbreak. If incompetence in the bad guys is your defense strategy, arguing the finer points of keys vs passwords seems pedantic. Mike Stone From aris.adamantiadis at belnet.be Thu Jan 7 00:27:17 2010 From: aris.adamantiadis at belnet.be (Aris Adamantiadis) Date: Wed, 06 Jan 2010 14:27:17 +0100 Subject: OpenSSH daemon security bug? In-Reply-To: <4B44789B.4030601@noaa.gov> References: <201001051501.11753.davi@leals.com> <531e3e4c1001050721h56f5b5f0n28a1aea6fa9f4dd6@mail.gmail.com> <4B437606.9020601@fifthhorseman.net> <904050dc-fa2c-11de-9b6a-001cc0cda50c@msgid.mathom.us> <4B43920F.7030502@noaa.gov> <59e4923e-fa39-11de-9b6a-001cc0cda50c@msgid.mathom.us> <4B43D09D.2080307@noaa.gov> <4B446427.1010100@belnet.be> <4B44789B.4030601@noaa.gov> Message-ID: <4B448FB5.40402@belnet.be> Jefferson Ogata a ?crit : > > That is true. But the vast majority of intruders are incompetent. > As Michael pointed out, that is not an argument for or against private keys. > As for your pen-testers, they had to get on the box with the private key > somehow before they could perform that attack. And they're pen-testers. That's why I don't encrypt the keys on my harddisk. If I'm owned, I'm own and that includes the private keys on my computer, be they encrypted or not. Of course I encrypt the whole hard disk of my laptop to protect my data, including keys, from laptop theft. > Have you ever seen this happen in a genuine intrusion? A pentest is a genuine intrusion, excepted that bad guys usually don't write a nice report after explaining what they compromised and how they got in. Private keys are a nice tool. You can't steal it even if somebody authenticated on your system with it, and you can't remember it if you happen to read a dump of the private key on somebody's computer. But the idea behind it is the same that the password things (It's something you know, not something you own or something you are). Aris From rees at merit.edu Wed Jan 6 23:40:22 2010 From: rees at merit.edu (Jim Rees) Date: Wed, 6 Jan 2010 07:40:22 -0500 Subject: smart cards (was: OpenSSH daemon security bug?) In-Reply-To: References: Message-ID: <20100106124022.GA5778@merit.edu> I thought the pkcs11 patches were already in. What's the hold up? Is it the PIN caching, separation into an agent, or something else? From mstone at mathom.us Thu Jan 7 00:03:05 2010 From: mstone at mathom.us (Michael Stone) Date: Wed, 06 Jan 2010 08:03:05 -0500 Subject: OpenSSH daemon security bug? In-Reply-To: <4B446427.1010100@belnet.be> References: <201001051501.11753.davi@leals.com> <531e3e4c1001050721h56f5b5f0n28a1aea6fa9f4dd6@mail.gmail.com> <4B437606.9020601@fifthhorseman.net> <904050dc-fa2c-11de-9b6a-001cc0cda50c@msgid.mathom.us> <4B43920F.7030502@noaa.gov> <59e4923e-fa39-11de-9b6a-001cc0cda50c@msgid.mathom.us> <4B43D09D.2080307@noaa.gov> <4B446427.1010100@belnet.be> Message-ID: <20100106130305.GC26761@mathom.us> On Wed, Jan 06, 2010 at 11:21:27AM +0100, Aris Adamantiadis wrote: >stealing the password (keypress sniffer, console sniffer, ...) or by >fetching the decrypted key in the user agent. Or using ssh-agent directly. A lot of people have those set up so they enter the passphrase once and then never again until they logout/reboot. Mike Stone From mstone at mathom.us Thu Jan 7 00:00:21 2010 From: mstone at mathom.us (Michael Stone) Date: Wed, 06 Jan 2010 08:00:21 -0500 Subject: OpenSSH daemon security bug? In-Reply-To: <4B4423E5.3010809@noaa.gov> References: <201001051501.11753.davi@leals.com> <531e3e4c1001050721h56f5b5f0n28a1aea6fa9f4dd6@mail.gmail.com> <4B437606.9020601@fifthhorseman.net> <887124.18116.qm@web31809.mail.mud.yahoo.com> <346374.24884.qm@web31815.mail.mud.yahoo.com> <4B4423E5.3010809@noaa.gov> Message-ID: <20100106130021.GB26761@mathom.us> On Wed, Jan 06, 2010 at 05:47:17AM +0000, Jefferson Ogata wrote: > I'm curious, since you bring it up, what method do you prefer for rate > limiting failed password attempts and failed pubkey attempts? How well > does your method work on routers and other ssh-capable network devices? Small installations can get by with firewall rules, and there are some pam modules and other techniques to do rate limiting and add exponential lockouts for accounts. People who actually need to worry about an attacker trying to really brute-force passwords (not dictionary attacks) probably should be using a real two factor mechanism and for those people this entire discussion is moot. (Gets back to "understand your threats".) > Also, are you suggesting that Jamie's statement is untrue? I won't challenge anyone else's experience, but I will say that I have not seen attackers trying to exhaustively brute-force passwords via ssh. Against a shadow file, sure, but the math on doing that over the network even with the default configuration that forces a new connection (and handshake overhead) after a few failures isn't pretty. You can make the math more favorable by doing it in a distributed fashion, and hitting a bunch of targets if they're using centralized auth, but in that case why wouldn't the centralized auth disable the account after, say, 1000 failures? At any rate, as above, if you're enough of a high-profile target that this is a serious risk to you, you should be using multifactor auth. Mike Stone From bob at proulx.com Thu Jan 7 03:35:59 2010 From: bob at proulx.com (Bob Proulx) Date: Wed, 6 Jan 2010 09:35:59 -0700 Subject: OpenSSH daemon security bug? In-Reply-To: <20100106130021.GB26761@mathom.us> References: <201001051501.11753.davi@leals.com> <531e3e4c1001050721h56f5b5f0n28a1aea6fa9f4dd6@mail.gmail.com> <4B437606.9020601@fifthhorseman.net> <887124.18116.qm@web31809.mail.mud.yahoo.com> <346374.24884.qm@web31815.mail.mud.yahoo.com> <4B4423E5.3010809@noaa.gov> <20100106130021.GB26761@mathom.us> Message-ID: <20100106163559.GA2717@dementia.proulx.com> Michael Stone wrote: > I won't challenge anyone else's experience, but I will say that I have > not seen attackers trying to exhaustively brute-force passwords via ssh. > Against a shadow file, sure, but the math on doing that over the network > even with the default configuration that forces a new connection (and > handshake overhead) after a few failures isn't pretty. My logs are filled with attackers trying dictionary guessing attacks. They have no hope of getting into the machine. I can't understand why they are even trying. But they *are* trying. To prevent most of the noise I use 'fail2ban'. For a long time that kept the noise in the log files low. > You can make the math more favorable by doing it in a distributed > fashion, and hitting a bunch of targets if they're using centralized > auth, but in that case why wouldn't the centralized auth disable the > account after, say, 1000 failures? Just very recently I started seeing attacks from a distributed set of IPs. Currently there is a 277 IP strong distributed attack engine that is probing constantly with a dictionary guessing attack. Also it is probing quite slowly. This is below the ban trigger threshold. But at that rate I can't imagine there to be any success from any victim system even if it used actually guessable logins and passwords. > At any rate, as above, if you're enough of a high-profile target > that this is a serious risk to you, you should be using multifactor > auth. The attacks I have been receiving have no hope of succeeding. So I am not worried. Just reporting that distributed attack engines have been seen in the wild. Their use will only increase over time. But this does not lower the security of ssh any. If external brute force attacks against passwords are the strongest attack then life is good and we are still winning. Bob From mstone at mathom.us Thu Jan 7 03:44:49 2010 From: mstone at mathom.us (Michael Stone) Date: Wed, 06 Jan 2010 11:44:49 -0500 Subject: OpenSSH daemon security bug? In-Reply-To: <20100106163559.GA2717@dementia.proulx.com> References: <201001051501.11753.davi@leals.com> <531e3e4c1001050721h56f5b5f0n28a1aea6fa9f4dd6@mail.gmail.com> <4B437606.9020601@fifthhorseman.net> <887124.18116.qm@web31809.mail.mud.yahoo.com> <346374.24884.qm@web31815.mail.mud.yahoo.com> <4B4423E5.3010809@noaa.gov> <20100106130021.GB26761@mathom.us> <20100106163559.GA2717@dementia.proulx.com> Message-ID: <7a18c256-fae2-11de-9b6a-001cc0cda50c@msgid.mathom.us> On Wed, Jan 06, 2010 at 09:35:59AM -0700, you wrote: >Michael Stone wrote: >> I won't challenge anyone else's experience, but I will say that I have >> not seen attackers trying to exhaustively brute-force passwords via ssh. >> Against a shadow file, sure, but the math on doing that over the network >> even with the default configuration that forces a new connection (and >> handshake overhead) after a few failures isn't pretty. > >My logs are filled with attackers trying dictionary guessing attacks. Agreed. (Dictionary guessing != exhaustive brute force.) >They have no hope of getting into the machine. I can't understand why >they are even trying. Because they're depressingly successful. It's absolutely amazing how many people will create an account like "bob" and give it a password like "fish". Mike Stone From dkg at fifthhorseman.net Thu Jan 7 04:12:09 2010 From: dkg at fifthhorseman.net (Daniel Kahn Gillmor) Date: Wed, 06 Jan 2010 12:12:09 -0500 Subject: OpenSSH daemon security bug? In-Reply-To: <201001061001.18572.davi@leals.com> References: <201001051501.11753.davi@leals.com> <49ba991c-fa0c-11de-9b6a-001cc0cda50c@msgid.mathom.us> <201001061001.18572.davi@leals.com> Message-ID: <4B44C469.2030904@fifthhorseman.net> On 01/06/2010 05:01 AM, Davi Diaz wrote: > Does "ssh -A" or any other parameters copy the private key to a remote host? No, the private key is never directly exposed to the remote host. However, "ssh -A" *will* expose the ability to *use* the private key to the remote host, unless your agent is configured to prompt the user before using the key ("ssh-add -c"). So for the duration of the connection, your account on the remote host (and of course the superuser account on the remote host) will have effective access to the key. But they will not be able to retain the key itself. In general, "ssh -A" is probably a bad idea. Most uses of "ssh -A" are better done with so-called "jump hosts", which allow you to still use the "star" pattern instead of the "chain" pattern of ssh connections. See Matt Taggart's "Good Practices for ssh" for more tips: http://lackof.org/taggart/hacking/ssh/ he describes a simple jumphost setup (look for ProxyCommand) on that page. Regards, --dkg -------------- next part -------------- A non-text attachment was scrubbed... Name: signature.asc Type: application/pgp-signature Size: 891 bytes Desc: OpenPGP digital signature URL: From davi at leals.com Thu Jan 7 05:29:59 2010 From: davi at leals.com (Davi Diaz) Date: Wed, 6 Jan 2010 18:29:59 +0000 Subject: OpenSSH daemon security bug? In-Reply-To: <4B44C469.2030904@fifthhorseman.net> References: <201001051501.11753.davi@leals.com> <201001061001.18572.davi@leals.com> <4B44C469.2030904@fifthhorseman.net> Message-ID: <201001061829.59971.davi@leals.com> Daniel Kahn Gillmor wrote: > "ssh -A" *will* expose the ability to *use* the private key to > the remote host, unless your agent is configured to prompt the user > before using the key ("ssh-add -c"). > > So [...] the remote host [...] will have effective access to the key. > See Matt Taggart's "Good Practices for ssh" for more tips: > http://lackof.org/taggart/hacking/ssh/ In my opinion this is a misfeature which removes any good from key based security because you depend on good practices. Back to use account-passwords access only. From jamie.beverly at yahoo.com Thu Jan 7 06:16:18 2010 From: jamie.beverly at yahoo.com (Jamie Beverly) Date: Wed, 6 Jan 2010 11:16:18 -0800 (PST) Subject: OpenSSH daemon security bug? In-Reply-To: <201001061829.59971.davi@leals.com> References: <201001051501.11753.davi@leals.com> <201001061001.18572.davi@leals.com> <4B44C469.2030904@fifthhorseman.net> <201001061829.59971.davi@leals.com> Message-ID: <435824.99816.qm@web31806.mail.mud.yahoo.com> ----- Original Message ---- > From: Davi Diaz > To: openssh-unix-dev at mindrot.org > Cc: Daniel Kahn Gillmor > Sent: Wed, January 6, 2010 10:29:59 AM > Subject: Re: OpenSSH daemon security bug? > > Daniel Kahn Gillmor wrote: > > "ssh -A" *will* expose the ability to *use* the private key to > > the remote host, unless your agent is configured to prompt the user > > before using the key ("ssh-add -c"). > > > > So [...] the remote host [...] will have effective access to the key. > > > > See Matt Taggart's "Good Practices for ssh" for more tips: > > http://lackof.org/taggart/hacking/ssh/ > > In my opinion this is a misfeature which removes any good from key based > security because you depend on good practices. Agent forwarding is a good thing in most cases. It prevents you from having to copy private keys around, but they do certainly have risks. If you forward your agent to a host with root users you do not trust (perhaps via privilege escalation), they will indeed be able to use your agent for the duration you remain connected. They will not be able to obtain your keys, be able to continue using your keys once you have disconnected (or lock your agent). They have never taken your authentication token itself. Compare this with the risk of connecting to a host with untrusted root users and entering a password. Here, your authentication token itself has been harvested, and when you disconnect, they still possess it, and can continue to use it. So while you should _NEVER_ forward an unlocked ssh-agent to a host you do not trust completely, it exposes only a temporary risk, and that risk is sufficiently less than requiring the transmission of the authentication token itself. > > Back to use account-passwords access only. > If the risks of public key authentication are too great for your use-case, going to a less secure mechanism will certainly not improve things. You should look to PKCS#11, RSA SecurID (one-time-passwords with PIN), or similar mechanisms. From davi at leals.com Thu Jan 7 08:45:18 2010 From: davi at leals.com (Davi Diaz) Date: Wed, 6 Jan 2010 21:45:18 +0000 Subject: OpenSSH daemon security bug? In-Reply-To: <435824.99816.qm@web31806.mail.mud.yahoo.com> References: <201001051501.11753.davi@leals.com> <201001061829.59971.davi@leals.com> <435824.99816.qm@web31806.mail.mud.yahoo.com> Message-ID: <201001062145.19473.davi@leals.com> Jamie Beverly wrote: > Agent forwarding is a good thing in most cases. It prevents you from having > to copy private keys around, but they do certainly have risks. If you > forward your agent to a host with root users you do not trust (perhaps via > privilege escalation), they will indeed be able to use your agent for the > duration you remain connected. > > They will not be able to obtain your keys, be able to continue using your > keys once you have disconnected (or lock your agent). They have never taken > your authentication token itself. I see the private key is never transfered, even if we use "ssh -A". Just the connection with the new host is started with the help of the original ssh client where the private key is. Could you point me to source code, the file or function which start such operation? > Compare this with the risk of connecting to a host with untrusted root > users and entering a password. Here, your authentication token itself > has been harvested, and when you disconnect, they still possess it, and > can continue to use it. From jamie.beverly at yahoo.com Thu Jan 7 09:12:11 2010 From: jamie.beverly at yahoo.com (Jamie Beverly) Date: Wed, 6 Jan 2010 14:12:11 -0800 (PST) Subject: OpenSSH daemon security bug? In-Reply-To: <201001062145.19473.davi@leals.com> References: <201001051501.11753.davi@leals.com> <201001061829.59971.davi@leals.com> <435824.99816.qm@web31806.mail.mud.yahoo.com> <201001062145.19473.davi@leals.com> Message-ID: <992485.17842.qm@web31809.mail.mud.yahoo.com> > > I see the private key is never transfered, even if we use "ssh -A". > > Just the connection with the new host is started with the help of the original > ssh client where the private key is. Could you point me to source code, the > file or function which start such operation? > Sure, a lot of the client meat for ssh-agent stuff is in authfd.c. on the client: sshconnect2.c:pubkey_prepare opens the agent connection, and iterates the key list, sshconnect2.c:userauth_pubkey does client portion of exchange; and auth2-pubkey.c:userauth_pubkey does the server side. You could also look at my project, http://pamsshagentauth.sourceforge.net/, to see another example of the same type of exchange. From davi at leals.com Thu Jan 7 09:17:28 2010 From: davi at leals.com (Davi Diaz) Date: Wed, 6 Jan 2010 22:17:28 +0000 Subject: OpenSSH daemon security bug? In-Reply-To: <992485.17842.qm@web31809.mail.mud.yahoo.com> References: <201001051501.11753.davi@leals.com> <201001062145.19473.davi@leals.com> <992485.17842.qm@web31809.mail.mud.yahoo.com> Message-ID: <201001062217.28731.davi@leals.com> Jamie Beverly wrote: > Sure, a lot of the client meat for ssh-agent stuff is in authfd.c. on the > client: sshconnect2.c:pubkey_prepare opens the agent connection, and > iterates the key list, sshconnect2.c:userauth_pubkey does client portion of > exchange; and auth2-pubkey.c:userauth_pubkey does the server side. > > You could also look at my project, http://pamsshagentauth.sourceforge.net/, > to see another example of the same type of exchange. Thanks a lot Jamie From bob at proulx.com Thu Jan 7 19:06:13 2010 From: bob at proulx.com (Bob Proulx) Date: Thu, 7 Jan 2010 01:06:13 -0700 Subject: OpenSSH daemon security bug? In-Reply-To: <7a18c256-fae2-11de-9b6a-001cc0cda50c@msgid.mathom.us> References: <531e3e4c1001050721h56f5b5f0n28a1aea6fa9f4dd6@mail.gmail.com> <4B437606.9020601@fifthhorseman.net> <887124.18116.qm@web31809.mail.mud.yahoo.com> <346374.24884.qm@web31815.mail.mud.yahoo.com> <4B4423E5.3010809@noaa.gov> <20100106130021.GB26761@mathom.us> <20100106163559.GA2717@dementia.proulx.com> <7a18c256-fae2-11de-9b6a-001cc0cda50c@msgid.mathom.us> Message-ID: <20100107080613.GA10176@discord.proulx.com> Michael Stone wrote: > Because they're depressingly successful. It's absolutely amazing how > many people will create an account like "bob" and give it a password > like "fish". Oh! I guess I should change my password now. :-) Bob From aris.adamantiadis at belnet.be Thu Jan 7 19:30:31 2010 From: aris.adamantiadis at belnet.be (Aris Adamantiadis) Date: Thu, 07 Jan 2010 09:30:31 +0100 Subject: OpenSSH daemon security bug? In-Reply-To: <20100107080613.GA10176@discord.proulx.com> References: <531e3e4c1001050721h56f5b5f0n28a1aea6fa9f4dd6@mail.gmail.com> <4B437606.9020601@fifthhorseman.net> <887124.18116.qm@web31809.mail.mud.yahoo.com> <346374.24884.qm@web31815.mail.mud.yahoo.com> <4B4423E5.3010809@noaa.gov> <20100106130021.GB26761@mathom.us> <20100106163559.GA2717@dementia.proulx.com> <7a18c256-fae2-11de-9b6a-001cc0cda50c@msgid.mathom.us> <20100107080613.GA10176@discord.proulx.com> Message-ID: <4B459BA7.4070902@belnet.be> Bob Proulx a ?crit : > Michael Stone wrote: >> Because they're depressingly successful. It's absolutely amazing how >> many people will create an account like "bob" and give it a password >> like "fish". > > Oh! I guess I should change my password now. :-) > > Bob Just in case, "cat" is not a convenient password either. :) From markus.r.friedl at arcor.de Thu Jan 7 22:34:31 2010 From: markus.r.friedl at arcor.de (Markus Friedl) Date: Thu, 7 Jan 2010 12:34:31 +0100 Subject: smart cards (was: OpenSSH daemon security bug?) In-Reply-To: <20100106124022.GA5778@merit.edu> References: <20100106124022.GA5778@merit.edu> Message-ID: <20100107113431.GA4944@folly> On Wed, Jan 06, 2010 at 07:40:22AM -0500, Jim Rees wrote: > I thought the pkcs11 patches were already in. What's the hold up? Is it > the PIN caching, separation into an agent, or something else? last time i checked there have been some issues, including the size of the patches, and that pkcs#11 support should replace both the old opensc and openbsd only (#define SMARTCARD) code. the obsolete code should go away. moreover, -# is a poor choice for a command line option; the problems with the agent protocol have not been resolved, etc. i'll try to work on this during the next weeks, but right now i don't have working pkcs#11/smartcard gear on openbsd. -m From markus.r.friedl at arcor.de Thu Jan 7 22:47:49 2010 From: markus.r.friedl at arcor.de (Markus Friedl) Date: Thu, 7 Jan 2010 12:47:49 +0100 Subject: Idea: reverse socks proxy In-Reply-To: <5F7A35DF-7D10-4647-9037-9294CF129459@dylanjay.com> References: <5F7A35DF-7D10-4647-9037-9294CF129459@dylanjay.com> Message-ID: <20100107114749.GA27224@folly> On Tue, Feb 17, 2009 at 02:10:20PM +1100, Dylan Jay wrote: > Just a usecase that I'm sure has been covered before but just in case > its not an openssh solution would be very helpful. > > I was trying to install software on a server that was firewalled so no > outbound http connections would work. I was also tunnelling via > another server. Outbound ssh connections also were a convenient option. > > What would have been nice would be a remote version of the dynamic > socks proxy ssh -D, so I could for instance > > > ssh me at remote --remote-socks=8123 > remote> export http_proxy=localhost:8123 > remote> wget --spider www.google.com this patch adds a ssh me at remote -R 8123 but it requires a patched server, too. Index: channels.c =================================================================== RCS file: /cvs/src/usr.bin/ssh/channels.c,v retrieving revision 1.299 diff -u -p -u -r1.299 channels.c --- channels.c 11 Nov 2009 21:37:03 -0000 1.299 +++ channels.c 7 Jan 2010 11:44:18 -0000 @@ -2684,7 +2684,8 @@ channel_request_remote_forwarding(const address_to_bind = listen_host; packet_start(SSH2_MSG_GLOBAL_REQUEST); - packet_put_cstring("tcpip-forward"); + packet_put_cstring(port_to_connect ? + "tcpip-forward" : "dynamic-forward at openssh.com"); packet_put_char(1); /* boolean: want reply */ packet_put_cstring(address_to_bind); packet_put_int(listen_port); @@ -3018,6 +3019,33 @@ channel_connect_to(const char *host, u_s return NULL; } return connect_to(host, port, ctype, rname); +} + +/* Process a direct-tcpip connect request. */ +Channel * +channel_input_direct_tcpip(void) +{ + Channel *c; + char *target, *originator; + u_short target_port, originator_port; + + target = packet_get_string(NULL); + target_port = packet_get_int(); + originator = packet_get_string(NULL); + originator_port = packet_get_int(); + packet_check_eom(); + + debug("channel_input_direct_tcpip: originator %s port %d, target %s " + "port %d", originator, originator_port, target, target_port); + + /* XXX check permission */ + c = channel_connect_to(target, target_port, + "direct-tcpip", "direct-tcpip"); + + xfree(originator); + xfree(target); + + return c; } void Index: channels.h =================================================================== RCS file: /cvs/src/usr.bin/ssh/channels.h,v retrieving revision 1.100 diff -u -p -u -r1.100 channels.h --- channels.h 11 Nov 2009 21:37:03 -0000 1.100 +++ channels.h 7 Jan 2010 11:44:18 -0000 @@ -242,6 +242,7 @@ void channel_clear_permitted_opens(void void channel_clear_adm_permitted_opens(void); void channel_print_adm_permitted_opens(void); int channel_input_port_forward_request(int, int); +Channel *channel_input_direct_tcpip(void); Channel *channel_connect_to(const char *, u_short, char *, char *); Channel *channel_connect_by_listen_address(u_short, char *, char *); int channel_request_remote_forwarding(const char *, u_short, Index: clientloop.c =================================================================== RCS file: /cvs/src/usr.bin/ssh/clientloop.c,v retrieving revision 1.215 diff -u -p -u -r1.215 clientloop.c --- clientloop.c 17 Nov 2009 05:31:44 -0000 1.215 +++ clientloop.c 7 Jan 2010 11:44:18 -0000 @@ -1784,6 +1784,8 @@ client_input_channel_open(int type, u_in c = client_request_forwarded_tcpip(ctype, rchan); } else if (strcmp(ctype, "x11") == 0) { c = client_request_x11(ctype, rchan); + } else if (strcmp(ctype, "direct-tcpip") == 0) { + c = channel_input_direct_tcpip(); } else if (strcmp(ctype, "auth-agent at openssh.com") == 0) { c = client_request_agent(ctype, rchan); } Index: readconf.c =================================================================== RCS file: /cvs/src/usr.bin/ssh/readconf.c,v retrieving revision 1.181 diff -u -p -u -r1.181 readconf.c --- readconf.c 29 Dec 2009 16:38:41 -0000 1.181 +++ readconf.c 7 Jan 2010 11:44:18 -0000 @@ -15,6 +15,7 @@ #include #include #include +#include #include @@ -39,6 +40,7 @@ #include "buffer.h" #include "kex.h" #include "mac.h" +#include "channels.h" /* Format of the configuration file: @@ -329,7 +331,7 @@ process_config_line(Options *options, co int *activep) { char *s, **charptr, *endofnumber, *keyword, *arg, *arg2, fwdarg[256]; - int opcode, *intptr, value, value2, scale; + int opcode, *intptr, value, value2, scale, remotefwd, dynamicfwd; LogLevel *log_level_ptr; long long orig, val64; size_t len; @@ -718,31 +720,38 @@ parse_int: fatal("%.200s line %d: Missing port argument.", filename, linenum); - if (opcode == oLocalForward || - opcode == oRemoteForward) { + remotefwd = (opcode == oRemoteForward); + dynamicfwd = (opcode == oDynamicForward); + + if (!dynamicfwd) { arg2 = strdelim(&s); - if (arg2 == NULL || *arg2 == '\0') - fatal("%.200s line %d: Missing target argument.", - filename, linenum); + if (arg2 == NULL || *arg2 == '\0') { + if (remotefwd) + dynamicfwd = 1; + else + fatal("%.200s line %d: Missing target " + "argument.", filename, linenum); + } /* construct a string for parse_forward */ snprintf(fwdarg, sizeof(fwdarg), "%s:%s", arg, arg2); - } else if (opcode == oDynamicForward) { - strlcpy(fwdarg, arg, sizeof(fwdarg)); } + if (dynamicfwd) + strlcpy(fwdarg, arg, sizeof(fwdarg)); - if (parse_forward(&fwd, fwdarg, - opcode == oDynamicForward ? 1 : 0, - opcode == oRemoteForward ? 1 : 0) == 0) + if (parse_forward(&fwd, fwdarg, dynamicfwd, remotefwd) == 0) fatal("%.200s line %d: Bad forwarding specification.", filename, linenum); if (*activep) { - if (opcode == oLocalForward || - opcode == oDynamicForward) - add_local_forward(options, &fwd); - else if (opcode == oRemoteForward) + if (remotefwd) { add_remote_forward(options, &fwd); + /* no restrictions for remote dynamic fwds */ + if (fwd.connect_port == 0) + channel_permit_all_opens(); + } else { + add_local_forward(options, &fwd); + } } break; Index: serverloop.c =================================================================== RCS file: /cvs/src/usr.bin/ssh/serverloop.c,v retrieving revision 1.159 diff -u -p -u -r1.159 serverloop.c --- serverloop.c 28 May 2009 16:50:16 -0000 1.159 +++ serverloop.c 7 Jan 2010 11:44:18 -0000 @@ -910,32 +910,6 @@ server_input_window_size(int type, u_int } static Channel * -server_request_direct_tcpip(void) -{ - Channel *c; - char *target, *originator; - u_short target_port, originator_port; - - target = packet_get_string(NULL); - target_port = packet_get_int(); - originator = packet_get_string(NULL); - originator_port = packet_get_int(); - packet_check_eom(); - - debug("server_request_direct_tcpip: originator %s port %d, target %s " - "port %d", originator, originator_port, target, target_port); - - /* XXX check permission */ - c = channel_connect_to(target, target_port, - "direct-tcpip", "direct-tcpip"); - - xfree(originator); - xfree(target); - - return c; -} - -static Channel * server_request_tun(void) { Channel *c = NULL; @@ -1026,7 +1000,7 @@ server_input_channel_open(int type, u_in if (strcmp(ctype, "session") == 0) { c = server_request_session(); } else if (strcmp(ctype, "direct-tcpip") == 0) { - c = server_request_direct_tcpip(); + c = channel_input_direct_tcpip(); } else if (strcmp(ctype, "tun at openssh.com") == 0) { c = server_request_tun(); } @@ -1069,7 +1043,8 @@ server_input_global_request(int type, u_ debug("server_input_global_request: rtype %s want_reply %d", rtype, want_reply); /* -R style forwarding */ - if (strcmp(rtype, "tcpip-forward") == 0) { + if (strcmp(rtype, "tcpip-forward") == 0 || + strcmp(rtype, "dynamic-forward at openssh.com") == 0) { struct passwd *pw; char *listen_address; u_short listen_port; @@ -1079,8 +1054,8 @@ server_input_global_request(int type, u_ fatal("server_input_global_request: no/invalid user"); listen_address = packet_get_string(NULL); listen_port = (u_short)packet_get_int(); - debug("server_input_global_request: tcpip-forward listen %s port %d", - listen_address, listen_port); + debug("server_input_global_request: %s listen %s port %d", + rtype, listen_address, listen_port); /* check permissions */ if (!options.allow_tcp_forwarding || @@ -1090,6 +1065,11 @@ server_input_global_request(int type, u_ pw->pw_uid != 0)) { success = 0; packet_send_debug("Server has disabled port forwarding."); + } else if (!strcmp(rtype, "dynamic-forward at openssh.com")) { + /* Listen on socks port */ + success = channel_setup_local_fwd_listener( + listen_address, listen_port, + "dynamic", 0, options.gateway_ports); } else { /* Start listening on the port */ success = channel_setup_remote_fwd_listener( Index: ssh.c =================================================================== RCS file: /cvs/src/usr.bin/ssh/ssh.c,v retrieving revision 1.329 diff -u -p -u -r1.329 ssh.c --- ssh.c 20 Dec 2009 07:28:36 -0000 1.329 +++ ssh.c 7 Jan 2010 11:44:18 -0000 @@ -454,8 +454,12 @@ main(int ac, char **av) break; case 'R': - if (parse_forward(&fwd, optarg, 0, 1)) { + if (parse_forward(&fwd, optarg, 0, 1) || + parse_forward(&fwd, optarg, 1, 1)) { add_remote_forward(&options, &fwd); + /* no restrictions for remote dynamic fwds */ + if (fwd.connect_port == 0) + channel_permit_all_opens(); } else { fprintf(stderr, "Bad remote forwarding specification " From alon.barlev at gmail.com Thu Jan 7 22:45:40 2010 From: alon.barlev at gmail.com (Alon Bar-Lev) Date: Thu, 7 Jan 2010 13:45:40 +0200 Subject: smart cards (was: OpenSSH daemon security bug?) In-Reply-To: <20100107113431.GA4944@folly> References: <20100106124022.GA5778@merit.edu> <20100107113431.GA4944@folly> Message-ID: <9e0cf0bf1001070345t780bab29l6dbfcbc133c6f473@mail.gmail.com> Why don't you have openbsd working with PKCS#11. I tested this a while back. On Thu, Jan 7, 2010 at 1:34 PM, Markus Friedl wrote: > On Wed, Jan 06, 2010 at 07:40:22AM -0500, Jim Rees wrote: >> I thought the pkcs11 patches were already in. ?What's the hold up? ?Is it >> the PIN caching, separation into an agent, or something else? > > last time i checked there have been some issues, including the size > of the patches, and that pkcs#11 support should replace both the > old opensc and openbsd only (#define SMARTCARD) code. the obsolete > code should go away. ?moreover, -# is a poor choice for a command > line option; the problems with the agent protocol have not been > resolved, etc. ?i'll try to work on this during the next weeks, but > right now i don't have working pkcs#11/smartcard gear on openbsd. > > -m > _______________________________________________ > openssh-unix-dev mailing list > openssh-unix-dev at mindrot.org > https://lists.mindrot.org/mailman/listinfo/openssh-unix-dev > From ed at 80386.nl Thu Jan 7 23:29:32 2010 From: ed at 80386.nl (Ed Schouten) Date: Thu, 7 Jan 2010 13:29:32 +0100 Subject: [openssh-portable] utmpx and ut_name In-Reply-To: <20091225165435.GW64905@hoeg.nl> References: <20091225165435.GW64905@hoeg.nl> Message-ID: <20100107122932.GO64905@hoeg.nl> Hi all, * Ed Schouten wrote: > The last couple of weeks I've been figuring out how hard it is to > replace FreeBSD's with . I've also noticed something else that's a bit strange. From defines.h: | # if defined(UTMPX_FILE) && !defined(DISABLE_UTMPX) | # define USE_UTMPX | # endif I've noticed some operating systems have UTMPX_FILE in , pointing to the file name used by the utmpx implementation. I'd rather not implement this, because the idea behind the utmpx API is to get rid of direct file access. Is there a reason why OpenSSH uses this definition? In my experimental utmpx-branch in the FreeBSD SVN repository I've changed the code to: | # if !defined(DISABLE_UTMPX) | # define USE_UTMPX | # endif Cheers, -- Ed Schouten WWW: http://80386.nl/ -------------- next part -------------- A non-text attachment was scrubbed... Name: not available Type: application/pgp-signature Size: 196 bytes Desc: not available URL: From dan at doxpara.com Thu Jan 7 23:42:03 2010 From: dan at doxpara.com (Dan Kaminsky) Date: Thu, 7 Jan 2010 13:42:03 +0100 Subject: Idea: reverse socks proxy In-Reply-To: <20100107114749.GA27224@folly> References: <5F7A35DF-7D10-4647-9037-9294CF129459@dylanjay.com> <20100107114749.GA27224@folly> Message-ID: This is super cool, but shouldn't require a server patch. A remote port forward should just come back to the client's socks parser, and the sockets should be provided locally instead of remotely. How are you using remote now? On Jan 7, 2010, at 12:47 PM, Markus Friedl wrote: > On Tue, Feb 17, 2009 at 02:10:20PM +1100, Dylan Jay wrote: >> Just a usecase that I'm sure has been covered before but just in case >> its not an openssh solution would be very helpful. >> >> I was trying to install software on a server that was firewalled so >> no >> outbound http connections would work. I was also tunnelling via >> another server. Outbound ssh connections also were a convenient >> option. >> >> What would have been nice would be a remote version of the dynamic >> socks proxy ssh -D, so I could for instance >> >>> ssh me at remote --remote-socks=8123 >> remote> export http_proxy=localhost:8123 >> remote> wget --spider www.google.com > > this patch adds a > ssh me at remote -R 8123 > but it requires a patched server, too. > > > Index: channels.c > =================================================================== > RCS file: /cvs/src/usr.bin/ssh/channels.c,v > retrieving revision 1.299 > diff -u -p -u -r1.299 channels.c > --- channels.c 11 Nov 2009 21:37:03 -0000 1.299 > +++ channels.c 7 Jan 2010 11:44:18 -0000 > @@ -2684,7 +2684,8 @@ channel_request_remote_forwarding(const > address_to_bind = listen_host; > > packet_start(SSH2_MSG_GLOBAL_REQUEST); > - packet_put_cstring("tcpip-forward"); > + packet_put_cstring(port_to_connect ? > + "tcpip-forward" : "dynamic-forward at openssh.com"); > packet_put_char(1); /* boolean: want reply */ > packet_put_cstring(address_to_bind); > packet_put_int(listen_port); > @@ -3018,6 +3019,33 @@ channel_connect_to(const char *host, u_s > return NULL; > } > return connect_to(host, port, ctype, rname); > +} > + > +/* Process a direct-tcpip connect request. */ > +Channel * > +channel_input_direct_tcpip(void) > +{ > + Channel *c; > + char *target, *originator; > + u_short target_port, originator_port; > + > + target = packet_get_string(NULL); > + target_port = packet_get_int(); > + originator = packet_get_string(NULL); > + originator_port = packet_get_int(); > + packet_check_eom(); > + > + debug("channel_input_direct_tcpip: originator %s port %d, > target %s " > + "port %d", originator, originator_port, target, target_port); > + > + /* XXX check permission */ > + c = channel_connect_to(target, target_port, > + "direct-tcpip", "direct-tcpip"); > + > + xfree(originator); > + xfree(target); > + > + return c; > } > > void > Index: channels.h > =================================================================== > RCS file: /cvs/src/usr.bin/ssh/channels.h,v > retrieving revision 1.100 > diff -u -p -u -r1.100 channels.h > --- channels.h 11 Nov 2009 21:37:03 -0000 1.100 > +++ channels.h 7 Jan 2010 11:44:18 -0000 > @@ -242,6 +242,7 @@ void channel_clear_permitted_opens(void > void channel_clear_adm_permitted_opens(void); > void channel_print_adm_permitted_opens(void); > int channel_input_port_forward_request(int, int); > +Channel *channel_input_direct_tcpip(void); > Channel *channel_connect_to(const char *, u_short, char *, char *); > Channel *channel_connect_by_listen_address(u_short, char *, char > *); > int channel_request_remote_forwarding(const char *, u_short, > Index: clientloop.c > =================================================================== > RCS file: /cvs/src/usr.bin/ssh/clientloop.c,v > retrieving revision 1.215 > diff -u -p -u -r1.215 clientloop.c > --- clientloop.c 17 Nov 2009 05:31:44 -0000 1.215 > +++ clientloop.c 7 Jan 2010 11:44:18 -0000 > @@ -1784,6 +1784,8 @@ client_input_channel_open(int type, u_in > c = client_request_forwarded_tcpip(ctype, rchan); > } else if (strcmp(ctype, "x11") == 0) { > c = client_request_x11(ctype, rchan); > + } else if (strcmp(ctype, "direct-tcpip") == 0) { > + c = channel_input_direct_tcpip(); > } else if (strcmp(ctype, "auth-agent at openssh.com") == 0) { > c = client_request_agent(ctype, rchan); > } > Index: readconf.c > =================================================================== > RCS file: /cvs/src/usr.bin/ssh/readconf.c,v > retrieving revision 1.181 > diff -u -p -u -r1.181 readconf.c > --- readconf.c 29 Dec 2009 16:38:41 -0000 1.181 > +++ readconf.c 7 Jan 2010 11:44:18 -0000 > @@ -15,6 +15,7 @@ > #include > #include > #include > +#include > > #include > > @@ -39,6 +40,7 @@ > #include "buffer.h" > #include "kex.h" > #include "mac.h" > +#include "channels.h" > > /* Format of the configuration file: > > @@ -329,7 +331,7 @@ process_config_line(Options *options, co > int *activep) > { > char *s, **charptr, *endofnumber, *keyword, *arg, *arg2, fwdarg > [256]; > - int opcode, *intptr, value, value2, scale; > + int opcode, *intptr, value, value2, scale, remotefwd, dynamicfwd; > LogLevel *log_level_ptr; > long long orig, val64; > size_t len; > @@ -718,31 +720,38 @@ parse_int: > fatal("%.200s line %d: Missing port argument.", > filename, linenum); > > - if (opcode == oLocalForward || > - opcode == oRemoteForward) { > + remotefwd = (opcode == oRemoteForward); > + dynamicfwd = (opcode == oDynamicForward); > + > + if (!dynamicfwd) { > arg2 = strdelim(&s); > - if (arg2 == NULL || *arg2 == '\0') > - fatal("%.200s line %d: Missing target argument.", > - filename, linenum); > + if (arg2 == NULL || *arg2 == '\0') { > + if (remotefwd) > + dynamicfwd = 1; > + else > + fatal("%.200s line %d: Missing target " > + "argument.", filename, linenum); > + } > > /* construct a string for parse_forward */ > snprintf(fwdarg, sizeof(fwdarg), "%s:%s", arg, arg2); > - } else if (opcode == oDynamicForward) { > - strlcpy(fwdarg, arg, sizeof(fwdarg)); > } > + if (dynamicfwd) > + strlcpy(fwdarg, arg, sizeof(fwdarg)); > > - if (parse_forward(&fwd, fwdarg, > - opcode == oDynamicForward ? 1 : 0, > - opcode == oRemoteForward ? 1 : 0) == 0) > + if (parse_forward(&fwd, fwdarg, dynamicfwd, remotefwd) == 0) > fatal("%.200s line %d: Bad forwarding specification.", > filename, linenum); > > if (*activep) { > - if (opcode == oLocalForward || > - opcode == oDynamicForward) > - add_local_forward(options, &fwd); > - else if (opcode == oRemoteForward) > + if (remotefwd) { > add_remote_forward(options, &fwd); > + /* no restrictions for remote dynamic fwds */ > + if (fwd.connect_port == 0) > + channel_permit_all_opens(); > + } else { > + add_local_forward(options, &fwd); > + } > } > break; > > Index: serverloop.c > =================================================================== > RCS file: /cvs/src/usr.bin/ssh/serverloop.c,v > retrieving revision 1.159 > diff -u -p -u -r1.159 serverloop.c > --- serverloop.c 28 May 2009 16:50:16 -0000 1.159 > +++ serverloop.c 7 Jan 2010 11:44:18 -0000 > @@ -910,32 +910,6 @@ server_input_window_size(int type, u_int > } > > static Channel * > -server_request_direct_tcpip(void) > -{ > - Channel *c; > - char *target, *originator; > - u_short target_port, originator_port; > - > - target = packet_get_string(NULL); > - target_port = packet_get_int(); > - originator = packet_get_string(NULL); > - originator_port = packet_get_int(); > - packet_check_eom(); > - > - debug("server_request_direct_tcpip: originator %s port %d, > target %s " > - "port %d", originator, originator_port, target, target_port); > - > - /* XXX check permission */ > - c = channel_connect_to(target, target_port, > - "direct-tcpip", "direct-tcpip"); > - > - xfree(originator); > - xfree(target); > - > - return c; > -} > - > -static Channel * > server_request_tun(void) > { > Channel *c = NULL; > @@ -1026,7 +1000,7 @@ server_input_channel_open(int type, u_in > if (strcmp(ctype, "session") == 0) { > c = server_request_session(); > } else if (strcmp(ctype, "direct-tcpip") == 0) { > - c = server_request_direct_tcpip(); > + c = channel_input_direct_tcpip(); > } else if (strcmp(ctype, "tun at openssh.com") == 0) { > c = server_request_tun(); > } > @@ -1069,7 +1043,8 @@ server_input_global_request(int type, u_ > debug("server_input_global_request: rtype %s want_reply %d", > rtype, want_reply); > > /* -R style forwarding */ > - if (strcmp(rtype, "tcpip-forward") == 0) { > + if (strcmp(rtype, "tcpip-forward") == 0 || > + strcmp(rtype, "dynamic-forward at openssh.com") == 0) { > struct passwd *pw; > char *listen_address; > u_short listen_port; > @@ -1079,8 +1054,8 @@ server_input_global_request(int type, u_ > fatal("server_input_global_request: no/invalid user"); > listen_address = packet_get_string(NULL); > listen_port = (u_short)packet_get_int(); > - debug("server_input_global_request: tcpip-forward listen %s > port %d", > - listen_address, listen_port); > + debug("server_input_global_request: %s listen %s port %d", > + rtype, listen_address, listen_port); > > /* check permissions */ > if (!options.allow_tcp_forwarding || > @@ -1090,6 +1065,11 @@ server_input_global_request(int type, u_ > pw->pw_uid != 0)) { > success = 0; > packet_send_debug("Server has disabled port forwarding."); > + } else if (!strcmp(rtype, "dynamic-forward at openssh.com")) { > + /* Listen on socks port */ > + success = channel_setup_local_fwd_listener( > + listen_address, listen_port, > + "dynamic", 0, options.gateway_ports); > } else { > /* Start listening on the port */ > success = channel_setup_remote_fwd_listener( > Index: ssh.c > =================================================================== > RCS file: /cvs/src/usr.bin/ssh/ssh.c,v > retrieving revision 1.329 > diff -u -p -u -r1.329 ssh.c > --- ssh.c 20 Dec 2009 07:28:36 -0000 1.329 > +++ ssh.c 7 Jan 2010 11:44:18 -0000 > @@ -454,8 +454,12 @@ main(int ac, char **av) > break; > > case 'R': > - if (parse_forward(&fwd, optarg, 0, 1)) { > + if (parse_forward(&fwd, optarg, 0, 1) || > + parse_forward(&fwd, optarg, 1, 1)) { > add_remote_forward(&options, &fwd); > + /* no restrictions for remote dynamic fwds */ > + if (fwd.connect_port == 0) > + channel_permit_all_opens(); > } else { > fprintf(stderr, > "Bad remote forwarding specification " > _______________________________________________ > openssh-unix-dev mailing list > openssh-unix-dev at mindrot.org > https://lists.mindrot.org/mailman/listinfo/openssh-unix-dev From markus.r.friedl at arcor.de Fri Jan 8 02:50:09 2010 From: markus.r.friedl at arcor.de (Markus Friedl) Date: Thu, 7 Jan 2010 16:50:09 +0100 Subject: Idea: reverse socks proxy In-Reply-To: References: <5F7A35DF-7D10-4647-9037-9294CF129459@dylanjay.com> <20100107114749.GA27224@folly> Message-ID: <20100107155009.GA1941@folly> On Thu, Jan 07, 2010 at 01:42:03PM +0100, Dan Kaminsky wrote: > This is super cool, but shouldn't require a server patch. A remote > port forward should just come back to the client's socks parser, and > the sockets should be provided locally instead of remotely. How are > you using remote now? this simple patch just reuses the client socks decoder on the server side. i also tried doing the parsing on the client side instead, but it requires much much more changes in the ssh channel code, so i'd rather avoid this. From cmadams at hiwaay.net Fri Jan 8 02:16:00 2010 From: cmadams at hiwaay.net (Chris Adams) Date: Thu, 7 Jan 2010 09:16:00 -0600 Subject: OpenSSH daemon security bug? In-Reply-To: <20100107080613.GA10176@discord.proulx.com> References: <4B437606.9020601@fifthhorseman.net> <887124.18116.qm@web31809.mail.mud.yahoo.com> <346374.24884.qm@web31815.mail.mud.yahoo.com> <4B4423E5.3010809@noaa.gov> <20100106130021.GB26761@mathom.us> <20100106163559.GA2717@dementia.proulx.com> <7a18c256-fae2-11de-9b6a-001cc0cda50c@msgid.mathom.us> <20100107080613.GA10176@discord.proulx.com> Message-ID: <20100107151600.GB1549798@hiwaay.net> Once upon a time, Bob Proulx said: > Michael Stone wrote: > > Because they're depressingly successful. It's absolutely amazing how > > many people will create an account like "bob" and give it a password > > like "fish". > > Oh! I guess I should change my password now. :-) "12345? That's amazing - I've got the same combination on my luggage!" -- Chris Adams Systems and Network Administrator - HiWAAY Internet Services I don't speak for anybody but myself - that's enough trouble. From dan at doxpara.com Fri Jan 8 03:44:17 2010 From: dan at doxpara.com (Dan Kaminsky) Date: Thu, 7 Jan 2010 17:44:17 +0100 Subject: Idea: reverse socks proxy In-Reply-To: <20100107155009.GA1941@folly> References: <5F7A35DF-7D10-4647-9037-9294CF129459@dylanjay.com> <20100107114749.GA27224@folly> <20100107155009.GA1941@folly> Message-ID: <4B36721B-350F-440C-A482-A0763024C299@doxpara.com> On Jan 7, 2010, at 4:50 PM, Markus Friedl wrote: > On Thu, Jan 07, 2010 at 01:42:03PM +0100, Dan Kaminsky wrote: >> This is super cool, but shouldn't require a server patch. A remote >> port forward should just come back to the client's socks parser, and >> the sockets should be provided locally instead of remotely. How are >> you using remote now? > > this simple patch just reuses the client socks decoder on > the server side. > > i also tried doing the parsing on the client side instead, but it > requires much much more changes in the ssh channel code, so i'd > rather avoid this. A lot of why -D worked is because you didn't need to patch servers. Couldn't we just do exactly what we did to -L, where the listener is lazy in determining socket destination and uses the SOCKS parser for determining target? From markus.r.friedl at arcor.de Fri Jan 8 08:31:52 2010 From: markus.r.friedl at arcor.de (Markus Friedl) Date: Thu, 7 Jan 2010 22:31:52 +0100 Subject: Idea: reverse socks proxy In-Reply-To: <4B36721B-350F-440C-A482-A0763024C299@doxpara.com> References: <5F7A35DF-7D10-4647-9037-9294CF129459@dylanjay.com> <20100107114749.GA27224@folly> <20100107155009.GA1941@folly> <4B36721B-350F-440C-A482-A0763024C299@doxpara.com> Message-ID: <20100107213152.GA23493@folly> On Thu, Jan 07, 2010 at 05:44:17PM +0100, Dan Kaminsky wrote: > Couldn't we just do exactly what we did to -L, where the listener is > lazy in determining socket destination and uses the SOCKS parser for > determining target? yes, this is what this patch is doing, but just on the server side. as i said before, this could be done on the client side, too, but requires much more code changes (and risks breaking other things). From dan at doxpara.com Fri Jan 8 08:51:46 2010 From: dan at doxpara.com (Dan Kaminsky) Date: Thu, 7 Jan 2010 22:51:46 +0100 Subject: Idea: reverse socks proxy In-Reply-To: <20100107213152.GA23493@folly> References: <5F7A35DF-7D10-4647-9037-9294CF129459@dylanjay.com> <20100107114749.GA27224@folly> <20100107155009.GA1941@folly> <4B36721B-350F-440C-A482-A0763024C299@doxpara.com> <20100107213152.GA23493@folly> Message-ID: On Thu, Jan 7, 2010 at 10:31 PM, Markus Friedl wrote: > On Thu, Jan 07, 2010 at 05:44:17PM +0100, Dan Kaminsky wrote: > > Couldn't we just do exactly what we did to -L, where the listener is > > lazy in determining socket destination and uses the SOCKS parser for > > determining target? > > yes, this is what this patch is doing, but just on the server side. > as i said before, this could be done on the client side, too, but > requires much more code changes (and risks breaking other things). > It's not a very useful patch if it's server-side only. Anything that requires a client and a server to patch is fundamentally less useful than something that just requires a client patch. I wouldn't want to see a patch like this in the codebase, for example, while I would want to see the client-only version. Meh. Maybe I'll try to take a crack at this next week. --Dan P.S. Sort of amazing to me that, ten years later, it's still the same krew around here :) From lode_leroy at hotmail.com Fri Jan 8 04:00:16 2010 From: lode_leroy at hotmail.com (lode leroy) Date: Thu, 7 Jan 2010 18:00:16 +0100 Subject: 5.1p1 and X11 forwarding failing Message-ID: in reply to: 5.1p1 and X11 forwarding failing http://lists.mindrot.org/pipermail/openssh-unix-dev/2009-February/027183.html I have the impression that SSH is running xauth with a filename in a temporary directory that does not exist: local:~ $ ssh -vv user at remote ... debug2: x11_get_proto: /usr/bin/xauth -f /tmp/ssh-VskgWb3776/xauthfile generate :0 MIT-MAGIC-COOKIE-1 untrusted timeout 1200 2>/dev/null ... remote:~ $ /usr/bin/xauth -f /tmp/ssh-VskgWb3776/xauthfile generate :0 MIT-MAGIC-COOKIE-1 untrusted timeout 1200 /usr/bin/xauth:? error in locking authority file /tmp/ssh-VskgWb3776/xauthfile remote:~ $ mkdir /tmp/ssh-VskgWb3776 remote:~ $ /usr/bin/xauth -f /tmp/ssh-VskgWb3776/xauthfile generate :0 MIT-MAGIC-COOKIE-1 untrusted timeout 1200 /usr/bin/xauth:? creating new authority file /tmp/ssh-VskgWb3776/xauthfile _________________________________________________________________ Windows Live Hotmail: Your friends can get your Facebook updates, right from Hotmail?. http://www.microsoft.com/middleeast/windows/windowslive/see-it-in-action/social-network-basics.aspx?ocid=PID23461::T:WLMTAGL:ON:WL:en-xm:SI_SB_4:092009 From dtucker at zip.com.au Sat Jan 9 18:19:06 2010 From: dtucker at zip.com.au (Darren Tucker) Date: Sat, 09 Jan 2010 18:19:06 +1100 Subject: [openssh-portable] utmpx and ut_name In-Reply-To: <20091225165435.GW64905@hoeg.nl> References: <20091225165435.GW64905@hoeg.nl> Message-ID: <4B482DEA.5070500@zip.com.au> Ed Schouten wrote: [...] > Now ut_user deviates a bit from , where this field is normally > called ut_name. I've noticed most (all?) implementations just #define > ut_name to ut_user to keep things happy, but I'd rather not put this > obfuscation in any new implementation. Sounds reasonable, your patch has been applied and will be in the 5.4p1 release. Thanks. -- Darren Tucker (dtucker at zip.com.au) GPG key 8FF4FA69 / D9A3 86E9 7EEE AF4B B2D4 37C9 C982 80C7 8FF4 FA69 Good judgement comes with experience. Unfortunately, the experience usually comes from bad judgement. From Jan.Pechanec at Sun.COM Mon Jan 11 22:46:05 2010 From: Jan.Pechanec at Sun.COM (Jan Pechanec) Date: Mon, 11 Jan 2010 12:46:05 +0100 (CET) Subject: /etc/nologin must be world-readable which is not totally clear Message-ID: hi, the man page for sshd(1) says about /etc/nologin: "The file should be world-readable". However, nologin has no effect if it's not readable by the connecting user: if (pw->pw_uid) f = fopen(_PATH_NOLOGIN, "r"); if (f) { /* /etc/nologin exists. Print its contents and exit. */ ... ... return(254) if root has a stricter mask than 022 it can easily happen that /etc/nologin can have 0600 permissions, for example. The user would not be able to send the file's contents anyway but he/she can login. It can lead to situations that login is assumed to be prohibited to non-root users when it is not. I can file a bug in bugzilla and send a patch if you agree that it should be fixed. If this behaviour should be preserved, I suggest to update the man page, it should read "The file must be world-readable" in that case. cheers, J. -- Jan Pechanec http://blogs.sun.com/janp From mouring at eviladmin.org Tue Jan 12 01:53:00 2010 From: mouring at eviladmin.org (Ben Lindstrom) Date: Mon, 11 Jan 2010 08:53:00 -0600 Subject: /etc/nologin must be world-readable which is not totally clear In-Reply-To: References: Message-ID: Could have swore I filed one a few years ago on this when it was brought to my attention. Maybe I didn't, since I can't find it. - Ben On Jan 11, 2010, at 5:46 AM, Jan Pechanec wrote: > > hi, the man page for sshd(1) says about /etc/nologin: "The file > should be world-readable". However, nologin has no effect if it's not > readable by the connecting user: > > if (pw->pw_uid) > f = fopen(_PATH_NOLOGIN, "r"); > > if (f) { > /* /etc/nologin exists. Print its contents and exit. */ > ... > ... > return(254) > > if root has a stricter mask than 022 it can easily happen that > /etc/nologin can have 0600 permissions, for example. The user would not > be able to send the file's contents anyway but he/she can login. It can > lead to situations that login is assumed to be prohibited to non-root > users when it is not. > > I can file a bug in bugzilla and send a patch if you agree that > it should be fixed. If this behaviour should be preserved, I suggest to > update the man page, it should read "The file must be world-readable" in > that case. > > cheers, J. > > -- > Jan Pechanec > http://blogs.sun.com/janp > _______________________________________________ > openssh-unix-dev mailing list > openssh-unix-dev at mindrot.org > https://lists.mindrot.org/mailman/listinfo/openssh-unix-dev From joachim at joachimschipper.nl Tue Jan 12 11:06:15 2010 From: joachim at joachimschipper.nl (Joachim Schipper) Date: Tue, 12 Jan 2010 01:06:15 +0100 Subject: [patch] Make keys work again Message-ID: <20100112000614.GB27817@polymnia.jschipper.dynalias.net> This patch makes keys work again. This bug was introduced in r1.78: http://www.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/authfile.c.diff?r1=1.77;r2=1.78. Joachim Index: authfile.c =================================================================== RCS file: /usr/obsd-repos/src/usr.bin/ssh/authfile.c,v retrieving revision 1.78 diff -u -N -p authfile.c --- authfile.c 11 Jan 2010 04:46:45 -0000 1.78 +++ authfile.c 11 Jan 2010 22:35:04 -0000 @@ -552,8 +552,8 @@ key_load_private_type(int type, const char *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; From dtucker at zip.com.au Tue Jan 12 11:18:36 2010 From: dtucker at zip.com.au (Darren Tucker) Date: Tue, 12 Jan 2010 11:18:36 +1100 Subject: [patch] Make keys work again In-Reply-To: <20100112000614.GB27817@polymnia.jschipper.dynalias.net> References: <20100112000614.GB27817@polymnia.jschipper.dynalias.net> Message-ID: <4B4BBFDC.1070403@zip.com.au> Joachim Schipper wrote: > This patch makes keys work again. This bug was introduced in r1.78: > http://www.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/authfile.c.diff?r1=1.77;r2=1.78. Doh (*hangs head in shame*). Patch applied, thanks. -- Darren Tucker (dtucker at zip.com.au) GPG key 8FF4FA69 / D9A3 86E9 7EEE AF4B B2D4 37C9 C982 80C7 8FF4 FA69 Good judgement comes with experience. Unfortunately, the experience usually comes from bad judgement. From joachim at joachimschipper.nl Tue Jan 12 11:24:34 2010 From: joachim at joachimschipper.nl (Joachim Schipper) Date: Tue, 12 Jan 2010 01:24:34 +0100 Subject: [patch] Automatically add keys to agent Message-ID: <20100112002433.GC27817@polymnia.jschipper.dynalias.net> My keys are secured with a passphrase. That's good for security, but having to type the passphrase either at every login or at every invocation of ssh(1) is annoying. I know I could invoke ssh-add(1) just before invoking ssh(1), if I keep track of whether I invoked it already, or write some hacky scripts; but the rest of OpenSSH is wonderfully usable without any hacks. Hence, this patch. I'll just quote ssh_config(5): AddKeyToAgent If this option is set to ``yes'' and ssh-agent(1) is running, any keys unlocked with a password will be added to the agent (with the default lifetime). Setting this to ``ask'' will cause ssh to require confirmation using the SSH_ASKPASS program before the key is added (see ssh-add(1) for details). The argument must be ``yes'', ``ask'', or ``no''. The default is ``no''. Having more knobs isn't really useful, IMHO. Default lifetime is configurable via ssh-agent(1)'s -t flag, and if you want to confirm each key use you should be willing to live without this convenience feature. By the way, are there plans to replace ask_permission() (also used for other "ask" type options, e.g. ControlMaster) by something a little more user-friendly? Having to type "yes" works, but isn't exactly elegant. (Not volunteering here, I know nothing about X.) Please be gentle, but inspect thoroughly, as this is my first patch. Joachim P.S. Note that the patch to authfile.c I just posted should be applied before testing this patch. Index: readconf.c =================================================================== RCS file: /usr/obsd-repos/src/usr.bin/ssh/readconf.c,v retrieving revision 1.182 diff -u -N -p readconf.c --- readconf.c 9 Jan 2010 23:04:13 -0000 1.182 +++ readconf.c 11 Jan 2010 22:19:10 -0000 @@ -128,7 +128,7 @@ typedef enum { oSendEnv, oControlPath, oControlMaster, oHashKnownHosts, oTunnel, oTunnelDevice, oLocalCommand, oPermitLocalCommand, oVisualHostKey, oUseRoaming, oZeroKnowledgePasswordAuthentication, - oDeprecated, oUnsupported + oAddKey, oDeprecated, oUnsupported } OpCodes; /* Textual representations of the tokens. */ @@ -232,6 +232,7 @@ static struct { #else { "zeroknowledgepasswordauthentication", oUnsupported }, #endif + { "addkeytoagent", oAddKey }, { NULL, oBadOption } }; @@ -914,6 +915,10 @@ parse_int: intptr = &options->use_roaming; goto parse_flag; + case oAddKey: + intptr = &options->add_key; + goto parse_yesnoask; + case oDeprecated: debug("%s line %d: Deprecated option \"%s\"", filename, linenum, keyword); @@ -1064,6 +1069,7 @@ initialize_options(Options * options) options->local_command = NULL; options->permit_local_command = -1; options->use_roaming = -1; + options->add_key = -1; options->visual_host_key = -1; options->zero_knowledge_password_authentication = -1; } @@ -1202,6 +1208,8 @@ fill_default_options(Options * options) options->permit_local_command = 0; if (options->use_roaming == -1) options->use_roaming = 1; + if (options->add_key == -1) + options->add_key = 0; if (options->visual_host_key == -1) options->visual_host_key = 0; if (options->zero_knowledge_password_authentication == -1) Index: readconf.h =================================================================== RCS file: /usr/obsd-repos/src/usr.bin/ssh/readconf.h,v retrieving revision 1.81 diff -u -N -p readconf.h --- readconf.h 9 Jan 2010 23:04:13 -0000 1.81 +++ readconf.h 11 Jan 2010 22:19:18 -0000 @@ -125,6 +125,8 @@ typedef struct { int use_roaming; + int add_key; /* add keys to agent */ + } Options; #define SSHCTL_MASTER_NO 0 Index: ssh-agent.1 =================================================================== RCS file: /usr/obsd-repos/src/usr.bin/ssh/ssh-agent.1,v retrieving revision 1.49 diff -u -N -p ssh-agent.1 --- ssh-agent.1 22 Oct 2009 15:02:12 -0000 1.49 +++ ssh-agent.1 11 Jan 2010 23:39:47 -0000 @@ -109,6 +109,13 @@ When the command dies, so does the agent. .Pp The agent initially does not have any private keys. Keys are added using +.Xr ssh 1 +(see +.Cm AddKeyToAgent +in +.Xr ssh_config 5 +for details) +or .Xr ssh-add 1 . When executed without arguments, .Xr ssh-add 1 Index: ssh.1 =================================================================== RCS file: /usr/obsd-repos/src/usr.bin/ssh/ssh.1,v retrieving revision 1.290 diff -u -N -p ssh.1 --- ssh.1 11 Jan 2010 01:39:46 -0000 1.290 +++ ssh.1 11 Jan 2010 23:14:55 -0000 @@ -428,6 +428,7 @@ For full details of the options listed below, and thei .Xr ssh_config 5 . .Pp .Bl -tag -width Ds -offset indent -compact +.It AddKeyToAgent .It AddressFamily .It BatchMode .It BindAddress @@ -803,6 +804,10 @@ The most convenient way to use public key authenticati authentication agent. See .Xr ssh-agent 1 +and (optionally) the +.Cm AddKeyToAgent +directive in +.Xr ssh_config 5 for more information. .Pp Challenge-response authentication works as follows: Index: ssh_config.5 =================================================================== RCS file: /usr/obsd-repos/src/usr.bin/ssh/ssh_config.5,v retrieving revision 1.126 diff -u -N -p ssh_config.5 --- ssh_config.5 9 Jan 2010 23:04:13 -0000 1.126 +++ ssh_config.5 11 Jan 2010 23:31:21 -0000 @@ -116,6 +116,27 @@ a canonicalized host name before matching). See .Sx PATTERNS for more information on patterns. +.It Cm AddKeyToAgent +If this option is set to +.Dq yes +and +.Xr ssh-agent 1 +is running, any keys unlocked with a password will be added to the agent (with +the default lifetime). +Setting this to +.Dq ask +will cause ssh to require confirmation using the +.Ev SSH_ASKPASS +program before the key is added (see +.Xr ssh-add 1 +for details). +The argument must be +.Dq yes , +.Dq ask , +or +.Dq no . +The default is +.Dq no . .It Cm AddressFamily Specifies which address family to use when connecting. Valid arguments are Index: sshconnect1.c =================================================================== RCS file: /usr/obsd-repos/src/usr.bin/ssh/sshconnect1.c,v retrieving revision 1.70 diff -u -N -p sshconnect1.c --- sshconnect1.c 6 Nov 2006 21:25:28 -0000 1.70 +++ sshconnect1.c 11 Jan 2010 22:49:11 -0000 @@ -57,21 +57,15 @@ extern char *__progname; * authenticate using the agent. */ static int -try_agent_authentication(void) +try_agent_authentication(AuthenticationConnection *auth) { int type; char *comment; - AuthenticationConnection *auth; u_char response[16]; u_int i; Key *key; BIGNUM *challenge; - /* Get connection to the agent. */ - auth = ssh_get_authentication_connection(); - if (!auth) - return 0; - if ((challenge = BN_new()) == NULL) fatal("try_agent_authentication: BN_new failed"); /* Loop through identities served by the agent. */ @@ -134,7 +128,6 @@ try_agent_authentication(void) /* The server returns success if it accepted the authentication. */ if (type == SSH_SMSG_SUCCESS) { - ssh_close_authentication_connection(auth); BN_clear_free(challenge); debug("RSA authentication accepted by server."); return 1; @@ -144,7 +137,6 @@ try_agent_authentication(void) packet_disconnect("Protocol error waiting RSA auth response: %d", type); } - ssh_close_authentication_connection(auth); BN_clear_free(challenge); debug("RSA authentication using agent refused."); return 0; @@ -200,7 +192,7 @@ respond_to_rsa_challenge(BIGNUM * challenge, RSA * prv * the user using it. */ static int -try_rsa_authentication(int idx) +try_rsa_authentication(int idx, AuthenticationConnection *auth) { BIGNUM *challenge; Key *public, *private; @@ -293,6 +285,19 @@ try_rsa_authentication(int idx) return 0; } + /* + * Consider adding key to agent. We add keys for the default lifetime + * with no need to confirm each use. + */ + if (auth != NULL && (options.add_key == 1 || + (options.add_key == 2 && + ask_permission("Add key %s (%s) to agent?", authfile, comment)))) { + if (ssh_add_identity_constrained(auth, private, comment, 0, 0)) + debug("Identity added: %s (%s)", authfile, comment); + else + verbose("Error while adding identity!"); + } + /* Compute and send a response to the challenge. */ respond_to_rsa_challenge(challenge, private->rsa); @@ -670,6 +675,7 @@ ssh_userauth1(const char *local_user, const char *serv Sensitive *sensitive) { int i, type; + AuthenticationConnection *auth = NULL; if (supported_authentications == 0) fatal("ssh_userauth1: server supports no auth methods"); @@ -715,14 +721,15 @@ ssh_userauth1(const char *local_user, const char *serv * agent is tried first because no passphrase is needed for * it, whereas identity files may require passphrases. */ - if (try_agent_authentication()) + auth = ssh_get_authentication_connection(); + if (auth != NULL && try_agent_authentication(auth)) goto success; /* Try RSA authentication for each identity. */ for (i = 0; i < options.num_identity_files; i++) if (options.identity_keys[i] != NULL && options.identity_keys[i]->type == KEY_RSA1 && - try_rsa_authentication(i)) + try_rsa_authentication(i, auth)) goto success; } /* Try challenge response authentication if the server supports it. */ @@ -746,5 +753,6 @@ ssh_userauth1(const char *local_user, const char *serv /* NOTREACHED */ success: - return; /* need statement after label */ + if (auth) + ssh_close_authentication_connection(auth); } Index: sshconnect2.c =================================================================== RCS file: /usr/obsd-repos/src/usr.bin/ssh/sshconnect2.c,v retrieving revision 1.178 diff -u -N -p sshconnect2.c --- sshconnect2.c 11 Jan 2010 04:46:45 -0000 1.178 +++ sshconnect2.c 11 Jan 2010 23:12:38 -0000 @@ -244,7 +244,7 @@ void userauth(Authctxt *, char *); static int sign_and_send_pubkey(Authctxt *, Identity *); static void pubkey_prepare(Authctxt *); static void pubkey_cleanup(Authctxt *); -static Key *load_identity_file(char *); +static Key *load_identity_file(char *, AuthenticationConnection *); static Authmethod *authmethod_get(char *authlist); static Authmethod *authmethod_lookup(const char *name); @@ -1102,7 +1102,7 @@ input_userauth_jpake_server_confirm(int type, u_int32_ static int identity_sign(Identity *id, u_char **sigp, u_int *lenp, - u_char *data, u_int datalen) + u_char *data, u_int datalen, AuthenticationConnection *auth) { Key *prv; int ret; @@ -1118,7 +1118,7 @@ identity_sign(Identity *id, u_char **sigp, u_int *lenp if (id->isprivate || (id->key->flags & KEY_FLAG_EXT)) return (key_sign(id->key, sigp, lenp, data, datalen)); /* load the private key from the file */ - if ((prv = load_identity_file(id->filename)) == NULL) + if ((prv = load_identity_file(id->filename, auth)) == NULL) return (-1); ret = key_sign(prv, sigp, lenp, data, datalen); key_free(prv); @@ -1168,7 +1168,7 @@ sign_and_send_pubkey(Authctxt *authctxt, Identity *id) /* generate signature */ ret = identity_sign(id, &signature, &slen, - buffer_ptr(&b), buffer_len(&b)); + buffer_ptr(&b), buffer_len(&b), authctxt->agent); if (ret == -1) { xfree(blob); buffer_free(&b); @@ -1240,30 +1240,36 @@ send_pubkey_test(Authctxt *authctxt, Identity *id) } static Key * -load_identity_file(char *filename) +load_identity_file(char *filename, AuthenticationConnection *ac) { Key *private; - char prompt[300], *passphrase; - int perm_ok = 0, quit, i; + char prompt[300], *passphrase, *comment = NULL; + int perm_ok = 0, quit, i, allowed = 0; struct stat st; if (stat(filename, &st) < 0) { debug3("no such identity: %s", filename); return NULL; } - private = key_load_private_type(KEY_UNSPEC, filename, "", NULL, &perm_ok); - if (!perm_ok) + private = key_load_private_type(KEY_UNSPEC, filename, "", &comment, &perm_ok); + if (!perm_ok) { + if (comment) + xfree(comment); return NULL; + } if (private == NULL) { - if (options.batch_mode) + if (options.batch_mode) { + if (comment) + xfree(comment); return NULL; + } snprintf(prompt, sizeof prompt, "Enter passphrase for key '%.100s': ", filename); for (i = 0; i < options.number_of_password_prompts; i++) { passphrase = read_passphrase(prompt, 0); if (strcmp(passphrase, "") != 0) { private = key_load_private_type(KEY_UNSPEC, - filename, passphrase, NULL, NULL); + filename, passphrase, &comment, NULL); quit = 0; } else { debug2("no passphrase given, try next key"); @@ -1273,9 +1279,39 @@ load_identity_file(char *filename) xfree(passphrase); if (private != NULL || quit) break; + if (comment) + xfree(comment); debug2("bad passphrase given, try again..."); } } + + /* If we loaded the key and have an agent, consider adding key. */ + if (private == NULL || ac == NULL) { + if (comment) + xfree(comment); + return private; + } + if (options.add_key == 1) + allowed = 1; + if (options.add_key == 2) { + if (comment == NULL) + allowed = ask_permission("Add key %s to agent?", + filename); + else + allowed = ask_permission("Add key %s (%s) to agent?", + filename, comment); + } + + if (allowed) { + /* Add for default lifetime; do not confirm each use. */ + if (ssh_add_identity_constrained(ac, private, comment, 0, 0)) + debug("Identity added: %s (%s)", filename, comment); + else + debug("Error while adding identity!"); + } + + if (comment) + xfree(comment); return private; } @@ -1394,7 +1430,8 @@ userauth_pubkey(Authctxt *authctxt) sent = send_pubkey_test(authctxt, id); } else if (id->key == NULL) { debug("Trying private key: %s", id->filename); - id->key = load_identity_file(id->filename); + id->key = load_identity_file(id->filename, + authctxt->agent); if (id->key != NULL) { id->isprivate = 1; sent = sign_and_send_pubkey(authctxt, id); From dtucker at zip.com.au Tue Jan 12 12:24:20 2010 From: dtucker at zip.com.au (Darren Tucker) Date: Tue, 12 Jan 2010 12:24:20 +1100 Subject: /etc/nologin must be world-readable which is not totally clear In-Reply-To: References: Message-ID: <20100112012420.GA5620@gate.dtucker.net> On Mon, Jan 11, 2010 at 12:46:05PM +0100, Jan Pechanec wrote: > hi, the man page for sshd(1) says about /etc/nologin: "The file > should be world-readable". However, nologin has no effect if it's not > readable by the connecting user: I agree that the existence of an unreadable /etc/nologin should prevent logins since it's pretty clear that's the admin's intent, so it's a bug in the code not the docs. The simple solution is to check errno for EPERM. I'm about to apply the following patch which should cover it. Index: session.c =================================================================== RCS file: /cvs/src/usr.bin/ssh/session.c,v retrieving revision 1.249 diff -u -p -r1.249 session.c --- session.c 20 Nov 2009 00:15:41 -0000 1.249 +++ session.c 12 Jan 2010 00:27:21 -0000 @@ -1105,10 +1105,12 @@ do_nologin(struct passwd *pw) if (!login_getcapbool(lc, "ignorenologin", 0) && pw->pw_uid) f = fopen(login_getcapstr(lc, "nologin", _PATH_NOLOGIN, _PATH_NOLOGIN), "r"); - if (f) { + if (f != NULL || errno == EPERM) { /* /etc/nologin exists. Print its contents and exit. */ logit("User %.100s not allowed because %s exists", pw->pw_name, _PATH_NOLOGIN); + if (f == NULL) + exit(254); while (fgets(buf, sizeof(buf), f)) fputs(buf, stderr); fclose(f); -- Darren Tucker (dtucker at zip.com.au) GPG key 8FF4FA69 / D9A3 86E9 7EEE AF4B B2D4 37C9 C982 80C7 8FF4 FA69 Good judgement comes with experience. Unfortunately, the experience usually comes from bad judgement. From ldv at altlinux.org Tue Jan 12 12:29:05 2010 From: ldv at altlinux.org (Dmitry V. Levin) Date: Tue, 12 Jan 2010 04:29:05 +0300 Subject: /etc/nologin must be world-readable which is not totally clear In-Reply-To: <20100112012420.GA5620@gate.dtucker.net> References: <20100112012420.GA5620@gate.dtucker.net> Message-ID: <20100112012905.GC25659@wo.int.altlinux.org> On Tue, Jan 12, 2010 at 12:24:20PM +1100, Darren Tucker wrote: > On Mon, Jan 11, 2010 at 12:46:05PM +0100, Jan Pechanec wrote: > > hi, the man page for sshd(1) says about /etc/nologin: "The file > > should be world-readable". However, nologin has no effect if it's not > > readable by the connecting user: > > I agree that the existence of an unreadable /etc/nologin should prevent > logins since it's pretty clear that's the admin's intent, so it's a bug > in the code not the docs. > > The simple solution is to check errno for EPERM. I'm about to apply the > following patch which should cover it. Please check for EACCES, too. -- ldv -------------- next part -------------- A non-text attachment was scrubbed... Name: not available Type: application/pgp-signature Size: 198 bytes Desc: not available URL: From dtucker at zip.com.au Tue Jan 12 16:34:04 2010 From: dtucker at zip.com.au (Darren Tucker) Date: Tue, 12 Jan 2010 16:34:04 +1100 Subject: /etc/nologin must be world-readable which is not totally clear In-Reply-To: <20100112012905.GC25659@wo.int.altlinux.org> References: <20100112012420.GA5620@gate.dtucker.net> <20100112012905.GC25659@wo.int.altlinux.org> Message-ID: <20100112053404.GA26531@gate.dtucker.net> On Tue, Jan 12, 2010 at 04:29:05AM +0300, Dmitry V. Levin wrote: > On Tue, Jan 12, 2010 at 12:24:20PM +1100, Darren Tucker wrote: [...] > > The simple solution is to check errno for EPERM. I'm about to apply the > > following patch which should cover it. > > Please check for EACCES, too. Jan Pechanec pointed this out too. After thinking about it some more I'm mildly concerned about failure scenarios involving the other path components (eg root accidentally chmods /etc or something). After discussing it with Damien I thing we should add an explict stat(). Index: session.c =================================================================== RCS file: /cvs/src/usr.bin/ssh/session.c,v retrieving revision 1.250 diff -u -p -r1.250 session.c --- session.c 12 Jan 2010 01:31:05 -0000 1.250 +++ session.c 12 Jan 2010 03:39:03 -0000 @@ -1100,12 +1100,15 @@ static void do_nologin(struct passwd *pw) { FILE *f = NULL; - char buf[1024]; + char buf[1024], *nologin; + struct stat sb; - if (!login_getcapbool(lc, "ignorenologin", 0) && pw->pw_uid) - f = fopen(login_getcapstr(lc, "nologin", _PATH_NOLOGIN, - _PATH_NOLOGIN), "r"); - if (f != NULL || errno == EPERM) { + if (!login_getcapbool(lc, "ignorenologin", 0) && pw->pw_uid) { + nologin = login_getcapstr(lc, "nologin", _PATH_NOLOGIN, + _PATH_NOLOGIN); + f = fopen(nologin, "r"); + } + if (f != NULL || stat(nologin, &sb) == 0) { /* /etc/nologin exists. Print its contents and exit. */ logit("User %.100s not allowed because %s exists", pw->pw_name, _PATH_NOLOGIN); -- Darren Tucker (dtucker at zip.com.au) GPG key 8FF4FA69 / D9A3 86E9 7EEE AF4B B2D4 37C9 C982 80C7 8FF4 FA69 Good judgement comes with experience. Unfortunately, the experience usually comes from bad judgement. From dtucker at zip.com.au Tue Jan 12 17:36:56 2010 From: dtucker at zip.com.au (Darren Tucker) Date: Tue, 12 Jan 2010 17:36:56 +1100 Subject: /etc/nologin must be world-readable which is not totally clear In-Reply-To: <20100112053404.GA26531@gate.dtucker.net> References: <20100112012420.GA5620@gate.dtucker.net> <20100112012905.GC25659@wo.int.altlinux.org> <20100112053404.GA26531@gate.dtucker.net> Message-ID: <20100112063656.GA27701@gate.dtucker.net> On Tue, Jan 12, 2010 at 04:34:04PM +1100, Darren Tucker wrote: > On Tue, Jan 12, 2010 at 04:29:05AM +0300, Dmitry V. Levin wrote: > > On Tue, Jan 12, 2010 at 12:24:20PM +1100, Darren Tucker wrote: > [...] > > > The simple solution is to check errno for EPERM. I'm about to apply the > > > following patch which should cover it. > > > > Please check for EACCES, too. > > Jan Pechanec pointed this out too. After thinking about it some more I'm > mildly concerned about failure scenarios involving the other path > components (eg root accidentally chmods /etc or something). After > discussing it with Damien I thing we should add an explict stat(). Gah, wrong diff. This is the one I meant to send. Index: session.c =================================================================== RCS file: /cvs/src/usr.bin/ssh/session.c,v retrieving revision 1.250 diff -u -p -r1.250 session.c --- session.c 12 Jan 2010 01:31:05 -0000 1.250 +++ session.c 12 Jan 2010 04:27:37 -0000 @@ -1100,22 +1100,27 @@ static void do_nologin(struct passwd *pw) { FILE *f = NULL; - char buf[1024]; + char buf[1024], *nl, *def_nl = _PATH_NOLOGIN; + struct stat sb; - if (!login_getcapbool(lc, "ignorenologin", 0) && pw->pw_uid) - f = fopen(login_getcapstr(lc, "nologin", _PATH_NOLOGIN, - _PATH_NOLOGIN), "r"); - if (f != NULL || errno == EPERM) { - /* /etc/nologin exists. Print its contents and exit. */ - logit("User %.100s not allowed because %s exists", - pw->pw_name, _PATH_NOLOGIN); - if (f == NULL) - exit(254); + if (login_getcapbool(lc, "ignorenologin", 0) && pw->pw_uid) + return; + nl = login_getcapstr(lc, "nologin", def_nl, def_nl); + + if (stat(nl, &sb) == -1) { + if (nl != def_nl) + xfree(nl); + return; + } + + /* /etc/nologin exists. Print its contents if we can and exit. */ + logit("User %.100s not allowed because %s exists", pw->pw_name, nl); + if ((f = fopen(nl, "r")) != NULL) { while (fgets(buf, sizeof(buf), f)) fputs(buf, stderr); fclose(f); - exit(254); } + exit(254); } /* -- Darren Tucker (dtucker at zip.com.au) GPG key 8FF4FA69 / D9A3 86E9 7EEE AF4B B2D4 37C9 C982 80C7 8FF4 FA69 Good judgement comes with experience. Unfortunately, the experience usually comes from bad judgement. From joachim at joachimschipper.nl Tue Jan 12 20:08:27 2010 From: joachim at joachimschipper.nl (Joachim Schipper) Date: Tue, 12 Jan 2010 10:08:27 +0100 Subject: /etc/nologin must be world-readable which is not totally clear In-Reply-To: <20100112063656.GA27701@gate.dtucker.net> References: <20100112012420.GA5620@gate.dtucker.net> <20100112012905.GC25659@wo.int.altlinux.org> <20100112053404.GA26531@gate.dtucker.net> <20100112063656.GA27701@gate.dtucker.net> Message-ID: <20100112090826.GA14516@polymnia.jschipper.dynalias.net> On Tue, Jan 12, 2010 at 05:36:56PM +1100, Darren Tucker wrote: > On Tue, Jan 12, 2010 at 04:34:04PM +1100, Darren Tucker wrote: > > On Tue, Jan 12, 2010 at 04:29:05AM +0300, Dmitry V. Levin wrote: > > > On Tue, Jan 12, 2010 at 12:24:20PM +1100, Darren Tucker wrote: > > [...] > > > > The simple solution is to check errno for EPERM. I'm about to apply the > > > > following patch which should cover it. > > > > > > Please check for EACCES, too. > > > > Jan Pechanec pointed this out too. After thinking about it some more I'm > > mildly concerned about failure scenarios involving the other path > > components (eg root accidentally chmods /etc or something). After > > discussing it with Damien I thing we should add an explict stat(). > > Gah, wrong diff. This is the one I meant to send. This should work, but have you considered access(2)? Joachim > Index: session.c > =================================================================== > RCS file: /cvs/src/usr.bin/ssh/session.c,v > retrieving revision 1.250 > diff -u -p -r1.250 session.c > --- session.c 12 Jan 2010 01:31:05 -0000 1.250 > +++ session.c 12 Jan 2010 04:27:37 -0000 > @@ -1100,22 +1100,27 @@ static void > do_nologin(struct passwd *pw) > { > FILE *f = NULL; > - char buf[1024]; > + char buf[1024], *nl, *def_nl = _PATH_NOLOGIN; > + struct stat sb; > > - if (!login_getcapbool(lc, "ignorenologin", 0) && pw->pw_uid) > - f = fopen(login_getcapstr(lc, "nologin", _PATH_NOLOGIN, > - _PATH_NOLOGIN), "r"); > - if (f != NULL || errno == EPERM) { > - /* /etc/nologin exists. Print its contents and exit. */ > - logit("User %.100s not allowed because %s exists", > - pw->pw_name, _PATH_NOLOGIN); > - if (f == NULL) > - exit(254); > + if (login_getcapbool(lc, "ignorenologin", 0) && pw->pw_uid) > + return; > + nl = login_getcapstr(lc, "nologin", def_nl, def_nl); > + > + if (stat(nl, &sb) == -1) { > + if (nl != def_nl) > + xfree(nl); > + return; > + } > + > + /* /etc/nologin exists. Print its contents if we can and exit. */ > + logit("User %.100s not allowed because %s exists", pw->pw_name, nl); > + if ((f = fopen(nl, "r")) != NULL) { > while (fgets(buf, sizeof(buf), f)) > fputs(buf, stderr); > fclose(f); > - exit(254); > } > + exit(254); > } > > /* From dtucker at zip.com.au Wed Jan 13 10:01:34 2010 From: dtucker at zip.com.au (Darren Tucker) Date: Wed, 13 Jan 2010 10:01:34 +1100 Subject: /etc/nologin must be world-readable which is not totally clear In-Reply-To: <20100112090826.GA14516@polymnia.jschipper.dynalias.net> References: <20100112012420.GA5620@gate.dtucker.net> <20100112012905.GC25659@wo.int.altlinux.org> <20100112053404.GA26531@gate.dtucker.net> <20100112063656.GA27701@gate.dtucker.net> <20100112090826.GA14516@polymnia.jschipper.dynalias.net> Message-ID: <4B4CFF4E.9060300@zip.com.au> Joachim Schipper wrote: [...] > This should work, but have you considered access(2)? I did, but I think it has the same potential for false positives under unusual conditions that just checking the errno of the open() did. Basically I didn't want to lock people out unless we're 100% sure that the nologin file is there, hence the stat(). -- Darren Tucker (dtucker at zip.com.au) GPG key 8FF4FA69 / D9A3 86E9 7EEE AF4B B2D4 37C9 C982 80C7 8FF4 FA69 Good judgement comes with experience. Unfortunately, the experience usually comes from bad judgement. From ed at 80386.nl Thu Jan 14 08:04:39 2010 From: ed at 80386.nl (Ed Schouten) Date: Wed, 13 Jan 2010 22:04:39 +0100 Subject: [Patch] Make OpenSSH work with FreeBSD's utmpx implementation Message-ID: <20100113210439.GU64905@hoeg.nl> Hello all, The next version of FreeBSD will use utmpx for its user accounting database, as opposed to utmp which is used right now. Unfortunately wtmpx and lastlog handling isn't standardized at all, which is why we took the liberty to integrate all these databases into a compact API, where setutxdb() can be used to switch to a different database and getutxuser() to search for a USER_PROCESS matching a certain user. In addition, we don't define UTMPX_FILE, because we don't want to allow applications to interact with the database files directly. This definition seems to be required by OpenSSH, even though it is not used in a sensible way. The attached patch makes OpenSSH from CVS build on FreeBSD HEAD again. It would be nice if it could be incorporated into the next version. Greetings, -- Ed Schouten WWW: http://80386.nl/ -------------- next part -------------- A non-text attachment was scrubbed... Name: openssh.diff Type: text/x-diff Size: 4407 bytes Desc: not available URL: -------------- next part -------------- A non-text attachment was scrubbed... Name: not available Type: application/pgp-signature Size: 196 bytes Desc: not available URL: From djm at mindrot.org Thu Jan 14 14:31:45 2010 From: djm at mindrot.org (Damien Miller) Date: Thu, 14 Jan 2010 14:31:45 +1100 (EST) Subject: ssh(1) multiplexing rewrite Message-ID: Hi, At the n2k10 OpenBSD network hackathon, I finally got some time to clean up and rewrite the ssh(1) client multiplexing code. The attached diffs (one for portable OpenSSH, one for OpenBSD) are the result, and they need some testing. The revised multiplexing code uses a better protocol between the master and slave processes and I even bothered to write it up :) It tracks the control sockets (both the listener and connected sockets) using the channels architecture, so it is properly buffered and integrated into the ssh mainloop. The practical benefit of this is that multiplexing operations no longer block the ssh(1) master process. I have also put some effort into making it more difficult for an errant ssh(1) slave to crash a master by sending malformed mux requests, but there are probably a few fatal() call it could still hit. In any case, the mux code should be substantially more robust against slaves that crash or wig out part way through establishing a session. Finally, to demonstrate how the new framework makes it so much easier to add new message types, I added support for opening new port-forwardings from slave ssh(1)s. Any local, remote or dynamic forwards that the slave has, but the master doesn't will be automatically requested when a mux session is started. NB. this isn't complete to my satisfaction yet, in particular ExitOnForwardingFailure is not handled for slave ssh(1) processes yet. This is a fairly large change and since we are pretty close to release time, I'd prefer to test it heavily before committing it. I would greatly appreciate people testing this code and reporting back on the list. Please apply one of the attached diffs, rebuild and install ssh(1) (you needn't update the server or tools) and try to break it. Please note that the new mux protocol is incompatible with the existing one, so close any open master sessions before trying to use the new ssh(1). If you find a bug or other strangeness, please report it here or file a bug at https://bugzilla.mindrot.org/ and please include log messages and/or debug traces. Thanks! -d -------------- next part -------------- Index: PROTOCOL.mux =================================================================== RCS file: PROTOCOL.mux diff -N PROTOCOL.mux --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ PROTOCOL.mux 14 Jan 2010 03:07:48 -0000 @@ -0,0 +1,152 @@ +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) + +This document describes the multiplexing protocol used by ssh(1)'s +ControlMaster connection-sharing. + +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 + 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 server will then reply with MUX_S_OK, 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 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 + +The server replies with: + + uint32 MUX_S_ALIVE + uint32 server pid + +4. Remotely terminating a master + +A client may request that a master terminate immediately: + + uint32 MUX_C_TERMINATE + +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 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. + +5. Requesting closure of port forwards + +A client may request the master to establish a port forward: + + uint32 MUX_C_OPEN_FORWARD + 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. Status messages + +The MUX_S_OK message is empty: + + uint32 MUX_S_OK + +The MUX_S_PERMISSION_DENIED and MUX_S_FAILURE include a reason: + + uint32 MUX_S_PERMISSION_DENIED + string reason + + uint32 MUX_S_FAILURE + 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_FWD_LOCAL 1 +#define MUX_FWD_REMOTE 2 +#define MUX_FWD_DYNAMIC 3 + +$OpenBSD$ Index: channels.c =================================================================== RCS file: /cvs/src/usr.bin/ssh/channels.c,v retrieving revision 1.301 diff -u -p -r1.301 channels.c --- channels.c 11 Jan 2010 01:39:46 -0000 1.301 +++ channels.c 14 Jan 2010 03:07:48 -0000 @@ -235,7 +235,6 @@ channel_register_fds(Channel *c, int rfd 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; @@ -323,6 +322,9 @@ channel_new(char *ctype, int type, int r 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->delayed = 1; /* prevent call to channel_post handler */ TAILQ_INIT(&c->status_confirms); debug("channel %d: new [%s]", found, remote_name); @@ -365,11 +367,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); @@ -395,8 +396,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); @@ -518,6 +517,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: @@ -531,6 +531,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: @@ -562,6 +563,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: @@ -612,6 +615,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: @@ -622,12 +627,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: @@ -834,9 +839,6 @@ channel_pre_open(Channel *c, fd_set *rea 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 */ @@ -981,6 +983,26 @@ channel_pre_x11_open(Channel *c, fd_set } } +static void +channel_pre_mux_client(Channel *c, fd_set *readset, fd_set *writeset) +{ + if (c->istate == CHAN_INPUT_OPEN && + buffer_check_alloc(&c->input, CHAN_RBUF)) + FD_SET(c->rfd, readset); + if (c->istate == CHAN_INPUT_WAIT_DRAIN) { + /* clear buffer immediately - partial packet */ + buffer_clear(&c->input); + chan_ibuf_empty(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 @@ -1728,34 +1750,6 @@ channel_handle_efd(Channel *c, fd_set *r /* 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)) - 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) { if (c->type == SSH_CHANNEL_OPEN && @@ -1785,10 +1779,130 @@ channel_post_open(Channel *c, fd_set *re 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; + +/* debug3("%s: channel %d: entering, need %u have %u", + __func__, c->self, need, buffer_len(&c->input)); */ + 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); + } +/* debug3("%s: channel %d: done, need %u have %u", + __func__, c->self, need, buffer_len(&c->input)); */ + 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 && 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; + c->mux_rcb(c, c->mux_ctx); + } + + 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)) { + 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, NULL); + /* 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) @@ -1817,6 +1931,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; @@ -1825,6 +1941,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 Index: channels.h =================================================================== RCS file: /cvs/src/usr.bin/ssh/channels.h,v retrieving revision 1.102 diff -u -p -r1.102 channels.h --- channels.h 11 Jan 2010 01:39:46 -0000 1.102 +++ channels.h 14 Jan 2010 03:07:48 -0000 @@ -53,7 +53,9 @@ #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; @@ -81,6 +83,9 @@ struct channel_connect { struct addrinfo *ai, *aitop; }; +/* Callbacks for mux channels back into client-specific code */ +typedef void mux_callback_fn(struct Channel *, void *); + struct Channel { int type; /* channel type/state */ int self; /* my own channel identifier */ @@ -92,7 +97,7 @@ 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 client_tty; /* (client) TTY has been requested */ int force_drain; /* force close on iEOF */ @@ -141,6 +146,10 @@ 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; }; #define CHAN_EXTENDED_IGNORE 0 @@ -171,6 +180,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 Index: clientloop.c =================================================================== RCS file: /cvs/src/usr.bin/ssh/clientloop.c,v retrieving revision 1.216 diff -u -p -r1.216 clientloop.c --- clientloop.c 9 Jan 2010 05:04:24 -0000 1.216 +++ clientloop.c 14 Jan 2010 03:07:49 -0000 @@ -113,7 +113,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 @@ -138,7 +138,7 @@ static volatile sig_atomic_t received_si static int in_non_blocking_mode = 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. */ @@ -556,9 +556,6 @@ client_wait_until_can_do_something(fd_se 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 @@ -686,7 +683,7 @@ client_status_confirm(int type, Channel /* 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", @@ -830,6 +827,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 */ @@ -927,7 +925,7 @@ process_escapes(Channel *c, Buffer *bin, 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; @@ -937,7 +935,7 @@ process_escapes(Channel *c, Buffer *bin, 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 " @@ -982,7 +980,7 @@ process_escapes(Channel *c, Buffer *bin, continue; case '&': - if (c && c->ctl_fd != -1) + if (c && c->ctl_chan != -1) goto noescape; /* * Detach the program (continue to serve @@ -1033,7 +1031,7 @@ process_escapes(Channel *c, Buffer *bin, 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\ @@ -1082,7 +1080,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; @@ -1315,8 +1313,6 @@ client_loop(int have_pty, int escape_cha 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 */ @@ -1434,12 +1430,6 @@ client_loop(int have_pty, int escape_cha /* 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; @@ -1841,9 +1831,8 @@ client_input_channel_req(int type, u_int chan_rcvd_eow(c); } else if (strcmp(rtype, "exit-status") == 0) { exitval = packet_get_int(); - if (c->ctl_fd != -1) { - /* Dispatch to mux client */ - atomicio(vwrite, c->ctl_fd, &exitval, sizeof(exitval)); + if (c->ctl_chan != -1) { + mux_exit_message(c, exitval); success = 1; } else if (id == session_ident) { /* Record exit value of local session */ Index: clientloop.h =================================================================== RCS file: /cvs/src/usr.bin/ssh/clientloop.h,v retrieving revision 1.22 diff -u -p -r1.22 clientloop.h --- clientloop.h 12 Jun 2008 15:19:17 -0000 1.22 +++ clientloop.h 14 Jan 2010 03:07:49 -0000 @@ -56,7 +56,7 @@ typedef void global_confirm_cb(int, u_in 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 */ @@ -71,3 +71,4 @@ void client_register_global_confirm(glo void muxserver_listen(void); int muxserver_accept_control(void); void muxclient(const char *); +void mux_exit_message(Channel *, int); Index: mux.c =================================================================== RCS file: /cvs/src/usr.bin/ssh/mux.c,v retrieving revision 1.9 diff -u -p -r1.9 mux.c --- mux.c 9 Jan 2010 05:04:24 -0000 1.9 +++ mux.c 14 Jan 2010 03:07:49 -0000 @@ -17,21 +17,20 @@ /* ssh session multiplexing support */ +// XXX signal of slave passed to master + /* * 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 (after #3 obviously) + * - Maybe extension mechanisms for multi-X11/multi-agent forwarding + * - Document the mux mini-protocol somewhere. + * - 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 @@ -43,6 +42,7 @@ #include #include +#include #include #include #include @@ -53,6 +53,7 @@ #include #include +#include "atomicio.h" #include "xmalloc.h" #include "log.h" #include "ssh.h" @@ -77,13 +78,14 @@ extern int stdin_null_flag; extern char *host; extern int subsystem_flag; extern Buffer command; +extern volatile sig_atomic_t quit_pending; /* 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; @@ -102,268 +104,234 @@ static volatile sig_atomic_t muxclient_t /* 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; - - 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.sun_len = offsetof(struct sockaddr_un, sun_path) + - strlen(options.control_path) + 1; +struct mux_master_state { + enum { MUX_HELLO_SEND, MUX_HELLO_WAIT, MUX_UP, MUX_SESSION } conn_state; +}; - if (strlcpy(addr.sun_path, options.control_path, - sizeof(addr.sun_path)) >= sizeof(addr.sun_path)) - fatal("ControlPath too long"); +/* 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_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 + +/* type codes for MUX_C_OPEN_FORWARD and MUX_C_CLOSE_FORWARD */ +#define MUX_FWD_LOCAL 1 +#define MUX_FWD_REMOTE 2 +#define MUX_FWD_DYNAMIC 3 + +static void mux_session_confirm(int, void *); + +static int process_mux_master_hello(struct mux_master_state *, Channel *, + Buffer *, Buffer *); +static int process_mux_new_session(struct mux_master_state *, Channel *, + Buffer *, Buffer *); +static int process_mux_alive_check(struct mux_master_state *, Channel *, + Buffer *, Buffer *); +static int process_mux_terminate(struct mux_master_state *, Channel *, + Buffer *, Buffer *); +static int process_mux_open_forward(struct mux_master_state *, Channel *, + Buffer *, Buffer *); +static int process_mux_close_forward(struct mux_master_state *, Channel *, + Buffer *, Buffer *); + +static const struct { + u_int type; + int (*handler)(struct mux_master_state *, 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_FORWARD, process_mux_open_forward }, + { MUX_C_CLOSE_FORWARD, process_mux_close_forward }, + { 0, NULL } +}; - if ((muxserver_sock = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) - fatal("%s socket(): %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); - old_umask = umask(0177); - if (bind(muxserver_sock, (struct sockaddr *)&addr, addr.sun_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)); + 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 at openssh.com", 0); - packet_send(); - } + Channel *sc, *c = channel_by_id(cid); - 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); + 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) + debug2("%s: channel %d n session channel %d", + __func__, c->self, c->remote_id); + c->remote_id = -1; + sc->ctl_chan = -1; + chan_mark_dead(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; - } - if ((ver = buffer_get_char(&m)) != SSHMUX_VER) { - error("%s: wrong client version %d", __func__, ver); - buffer_free(&m); - close(client_fd); - return 0; - } + return 0; +} - allowed = 1; - mux_command = buffer_get_int(&m); - flags = buffer_get_int(&m); +/* Mux master protocol message handlers */ - buffer_clear(&m); +static int +process_mux_master_hello(struct mux_master_state *state, Channel *c, + Buffer *m, Buffer *r) +{ + u_int ver; - 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 (state->conn_state != MUX_HELLO_WAIT) { + error("%s: MUX_MSG_HELLO received in state MUX_UP", __func__); + return -1; + } + 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); + + /* 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); + + 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->conn_state = MUX_UP; + return 0; +} - /* 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; +static int +process_mux_new_session(struct mux_master_state *state, Channel *c, + Buffer *m, Buffer *r) +{ + Channel *nc; + struct mux_session_confirm_ctx *cctx; + char *cmd, *cp; + u_int i, j, len, env_len, escape_char, window, packetmax; + int new_fd[3]; + + if (state->conn_state != MUX_UP) { + error("%s: incorrect state %u (expected %u)", + __func__, state->conn_state, MUX_UP); + return -1; } - if (!allowed) { - error("Refused control connection"); - close(client_fd); - buffer_free(&m); - return 0; + /* Reply for SSHMUX_COMMAND_OPEN */ + cctx = xcalloc(1, sizeof(*cctx)); + cctx->term = NULL; + cmd = NULL; + if (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 (cctx->term != NULL) + xfree(cctx->term); + error("%s: malformed message", __func__); + return -1; } - 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; + 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; + } } - 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); + 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); - 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); /* 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++) @@ -374,38 +342,44 @@ muxserver_accept_control(void) xfree(cctx->env); xfree(cctx->term); buffer_free(&cctx->cmd); - close(client_fd); xfree(cctx); - return 0; + + /* prepare reply */ + buffer_put_int(r, MUX_S_FAILURE); + buffer_put_cstring(r, + "did not receive file descriptors"); + return -1; } } - debug2("%s: got fds stdin %d, stdout %d, stderr %d", __func__, + debug3("%s: got fds stdin %d, stdout %d, stderr %d", __func__, new_fd[0], new_fd[1], new_fd[2]); + if (options.control_master == SSHCTL_MASTER_ASK || + options.control_master == SSHCTL_MASTER_AUTO_ASK) { + if (!ask_permission("Allow shared connection to %s? ", host)) { + debug2("%s: session refused by user", __func__); + close(new_fd[0]); + close(new_fd[1]); + close(new_fd[2]); + xfree(cctx->term); + if (env_len != 0) { + for (i = 0; i < env_len; i++) + xfree(cctx->env[i]); + xfree(cctx->env); + } + buffer_free(&cctx->cmd); + /* prepare reply */ + buffer_put_int(r, MUX_S_PERMISSION_DENIED); + buffer_put_cstring(r, "Permission denied"); + return 0; + } + } + /* Try to pick up ttymodes from client before it goes raw */ if (cctx->want_tty && tcgetattr(new_fd[0], &cctx->tio) == -1) error("%s: tcgetattr: %s", __func__, strerror(errno)); - /* This roundtrip is just for synchronisation of ttymodes */ - buffer_clear(&m); - if (ssh_msg_send(client_fd, SSHMUX_VER, &m) == -1) { - error("%s: client msg_send failed", __func__); - close(client_fd); - close(new_fd[0]); - close(new_fd[1]); - close(new_fd[2]); - buffer_free(&m); - xfree(cctx->term); - if (env_len != 0) { - for (i = 0; i < env_len; i++) - xfree(cctx->env[i]); - xfree(cctx->env); - } - return 0; - } - buffer_free(&m); - /* enable nonblocking unless tty */ if (!isatty(new_fd[0])) set_nonblock(new_fd[0]); @@ -414,257 +388,963 @@ muxserver_accept_control(void) if (!isatty(new_fd[2])) set_nonblock(new_fd[2]); - set_nonblock(client_fd); - window = CHAN_SES_WINDOW_DEFAULT; packetmax = CHAN_SES_PACKET_DEFAULT; if (cctx->want_tty) { window >>= 1; packetmax >>= 1; } - - c = channel_new("session", SSH_CHANNEL_OPENING, + + nc = channel_new("session", SSH_CHANNEL_OPENING, new_fd[0], new_fd[1], new_fd[2], window, packetmax, CHAN_EXTENDED_WRITE, "client-session", /*nonblock*/0); - c->ctl_fd = client_fd; + nc->ctl_chan = c->self; /* link session -> control channel */ + c->remote_id = nc->self; /* link control -> session channel */ + if (cctx->want_tty && escape_char != 0xffffffff) { - channel_register_filter(c->self, + channel_register_filter(nc->self, client_simple_escape_filter, NULL, client_filter_cleanup, client_new_escape_filter_ctx((int)escape_char)); } - debug3("%s: channel_new: %d", __func__, c->self); - - channel_send_open(c->self); - channel_register_open_confirm(c->self, mux_session_confirm, cctx); - return 0; -} + debug2("%s: channel_new: %d linked to control channel %d", + __func__, nc->self, nc->ctl_chan); -/* ** Multiplexing client support */ + channel_send_open(nc->self); + channel_register_open_confirm(nc->self, mux_session_confirm, cctx); + channel_register_cleanup(nc->self, mux_master_session_cleanup_cb, 0); + + /* prepare reply */ + /* XXX defer until mux_session_confirm() fires */ + buffer_put_int(r, MUX_S_OK); + state->conn_state = MUX_SESSION; -/* Exit signal handler */ -static void -control_client_sighandler(int signo) -{ - muxclient_terminate = signo; + return 0; } -/* - * Relay signal handler - used to pass some signals from mux client to - * mux master. - */ -static void -control_client_sigrelay(int signo) +static int +process_mux_alive_check(struct mux_master_state *state, Channel *c, + Buffer *m, Buffer *r) { - int save_errno = errno; + debug2("%s: channel %d: alive check", __func__, c->self); - if (muxserver_pid > 1) - kill(muxserver_pid, signo); + /* prepare reply */ + buffer_put_int(r, MUX_S_ALIVE); + buffer_put_int(r, (u_int)getpid()); - errno = save_errno; + return 0; } -/* Check mux client environment variables before passing them to mux master. */ static int -env_permitted(char *env) +process_mux_terminate(struct mux_master_state *state, Channel *c, + Buffer *m, Buffer *r) { - int i, ret; - char name[1024], *cp; + debug2("%s: channel %d: terminate request", __func__, c->self); - if ((cp = strchr(env, '=')) == NULL || cp == env) - return (0); - ret = snprintf(name, sizeof(name), "%.*s", (int)(cp - env), env); - if (ret <= 0 || (size_t)ret >= sizeof(name)) - fatal("env_permitted: name '%.100s...' too long", env); - - for (i = 0; i < options.num_send_env; i++) - if (match_pattern(name, options.send_env[i])) - return (1); + if (options.control_master == SSHCTL_MASTER_ASK || + options.control_master == SSHCTL_MASTER_AUTO_ASK) { + if (!ask_permission("Terminate shared connection to %s? ", + host)) { + debug2("%s: termination refused by user", __func__); + buffer_put_int(r, MUX_S_PERMISSION_DENIED); + buffer_put_cstring(r, "Permission denied"); + return 0; + } + } - return (0); + quit_pending = 1; + buffer_put_int(r, MUX_S_OK); + /* XXX exit happens too soon - message never makes it to client */ + return 0; } -/* Multiplex client main loop. */ -void -muxclient(const char *path) +static char * +format_forward(u_int ftype, Forward *fwd) { - struct sockaddr_un addr; - int i, r, fd, sock, exitval[2], num_env; - Buffer m; - char *term; - extern char **environ; - u_int allowed, flags; + char *ret; - if (muxclient_command == 0) - muxclient_command = SSHMUX_COMMAND_OPEN; - - switch (options.control_master) { - case SSHCTL_MASTER_AUTO: - case SSHCTL_MASTER_AUTO_ASK: - debug("auto-mux: Trying existing master"); - /* FALLTHROUGH */ - case SSHCTL_MASTER_NO: + switch (ftype) { + case MUX_FWD_LOCAL: + xasprintf(&ret, "local forward %.200s:%d -> %.200s:%d", + (fwd->listen_host == NULL) ? + (options.gateway_ports ? "*" : "LOCALHOST") : + fwd->listen_host, fwd->listen_port, + fwd->connect_host, fwd->connect_port); + break; + case MUX_FWD_DYNAMIC: + xasprintf(&ret, "dynamic forward %.200s:%d -> *", + (fwd->listen_host == NULL) ? + (options.gateway_ports ? "*" : "LOCALHOST") : + fwd->listen_host, fwd->listen_port); + break; + case MUX_FWD_REMOTE: + xasprintf(&ret, "remote forward %.200s:%d -> %.200s:%d", + (fwd->listen_host == NULL) ? + "LOCALHOST" : fwd->listen_host, + fwd->listen_port, + fwd->connect_host, fwd->connect_port); break; default: - return; + fatal("%s: unknown forward type %u", __func__, ftype); } + return ret; +} - memset(&addr, '\0', sizeof(addr)); - addr.sun_family = AF_UNIX; - addr.sun_len = offsetof(struct sockaddr_un, sun_path) + - strlen(path) + 1; +static int +compare_host(const char *a, const char *b) +{ + if (a == NULL && b == NULL) + return 1; + if (a == NULL || b == NULL) + return 0; + return strcmp(a, b) == 0; +} - if (strlcpy(addr.sun_path, path, - sizeof(addr.sun_path)) >= sizeof(addr.sun_path)) - fatal("ControlPath too long"); +static int +compare_forward(Forward *a, Forward *b) +{ + if (!compare_host(a->listen_host, b->listen_host)) + return 0; + if (a->listen_port != b->listen_port) + return 0; + if (!compare_host(a->connect_host, b->connect_host)) + return 0; + if (a->connect_port != b->connect_port) + return 0; - if ((sock = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) - fatal("%s socket(): %s", __func__, strerror(errno)); + return 1; +} - if (connect(sock, (struct sockaddr *)&addr, addr.sun_len) == -1) { - if (muxclient_command != SSHMUX_COMMAND_OPEN) { - fatal("Control socket connect(%.100s): %s", path, - strerror(errno)); +static int +process_mux_open_forward(struct mux_master_state *state, Channel *c, + Buffer *m, Buffer *r) +{ + Forward fwd; + char *fwd_desc = NULL; + u_int ftype; + int i, ret = 0, freefwd = 1; + + fwd.listen_host = fwd.connect_host = NULL; + if (buffer_get_int_ret(&ftype, m) != 0 || + (fwd.listen_host = buffer_get_string_ret(m, NULL)) == NULL || + buffer_get_int_ret(&fwd.listen_port, m) != 0 || + (fwd.connect_host = buffer_get_string_ret(m, NULL)) == NULL || + buffer_get_int_ret(&fwd.connect_port, m) != 0) { + error("%s: malformed message", __func__); + ret = -1; + goto out; + } + + if (*fwd.listen_host == '\0') { + xfree(fwd.listen_host); + fwd.listen_host = NULL; + } + if (*fwd.connect_host == '\0') { + xfree(fwd.connect_host); + fwd.connect_host = NULL; + } + + debug2("%s: channel %d: request %s", __func__, c->self, + (fwd_desc = format_forward(ftype, &fwd))); + + if (ftype != MUX_FWD_LOCAL && ftype != MUX_FWD_REMOTE && + ftype != MUX_FWD_DYNAMIC) { + logit("%s: invalid forwarding type %u", __func__, ftype); + invalid: + xfree(fwd.listen_host); + xfree(fwd.connect_host); + buffer_put_int(r, MUX_S_FAILURE); + buffer_put_cstring(r, "Invalid forwarding request"); + return 0; + } + /* XXX support rport0 forwarding with reply of port assigned */ + if (fwd.listen_port == 0 || fwd.listen_port >= 65536) { + logit("%s: invalid listen port %u", __func__, + fwd.listen_port); + goto invalid; + } + if (fwd.connect_port >= 65536 || (ftype != MUX_FWD_DYNAMIC && + ftype != MUX_FWD_REMOTE && fwd.connect_port == 0)) { + logit("%s: invalid connect port %u", __func__, + fwd.connect_port); + goto invalid; + } + if (ftype != MUX_FWD_DYNAMIC && fwd.connect_host == NULL) { + logit("%s: missing connect host", __func__); + goto invalid; + } + + /* Skip forwards that have already been requested */ + switch (ftype) { + case MUX_FWD_LOCAL: + case MUX_FWD_DYNAMIC: + for (i = 0; i < options.num_local_forwards; i++) { + if (compare_forward(&fwd, + options.local_forwards + i)) { + exists: + debug2("%s: found existing forwarding", + __func__); + buffer_put_int(r, MUX_S_OK); + goto out; + } } - if (errno == ENOENT) - debug("Control socket \"%.100s\" does not exist", path); - else { - error("Control socket connect(%.100s): %s", path, - strerror(errno)); + break; + case MUX_FWD_REMOTE: + for (i = 0; i < options.num_remote_forwards; i++) { + if (compare_forward(&fwd, + options.remote_forwards + i)) + goto exists; } - close(sock); + break; + } + + if (options.control_master == SSHCTL_MASTER_ASK || + options.control_master == SSHCTL_MASTER_AUTO_ASK) { + if (!ask_permission("Open %s on %s?", fwd_desc, host)) { + debug2("%s: forwarding refused by user", __func__); + buffer_put_int(r, MUX_S_PERMISSION_DENIED); + buffer_put_cstring(r, "Permission denied"); + goto out; + } + } + + if (ftype == MUX_FWD_LOCAL || ftype == MUX_FWD_DYNAMIC) { + if (options.num_local_forwards + 1 >= + SSH_MAX_FORWARDS_PER_DIRECTION || + channel_setup_local_fwd_listener(fwd.listen_host, + fwd.listen_port, fwd.connect_host, fwd.connect_port, + options.gateway_ports) < 0) { + fail: + logit("slave-requested %s failed", fwd_desc); + buffer_put_int(r, MUX_S_FAILURE); + buffer_put_cstring(r, "Port forwarding failed"); + goto out; + } + add_local_forward(&options, &fwd); + freefwd = 0; + } else { + /* XXX wait for remote to confirm */ + if (options.num_remote_forwards + 1 >= + SSH_MAX_FORWARDS_PER_DIRECTION || + channel_request_remote_forwarding(fwd.listen_host, + fwd.listen_port, fwd.connect_host, fwd.connect_port) < 0) + goto fail; + add_remote_forward(&options, &fwd); + freefwd = 0; + } + buffer_put_int(r, MUX_S_OK); + out: + if (fwd_desc != NULL) + xfree(fwd_desc); + if (freefwd) { + if (fwd.listen_host != NULL) + xfree(fwd.listen_host); + if (fwd.connect_host != NULL) + xfree(fwd.connect_host); + } + return ret; +} + +static int +process_mux_close_forward(struct mux_master_state *state, Channel *c, + Buffer *m, Buffer *r) +{ + Forward fwd; + char *fwd_desc = NULL; + u_int ftype; + int ret = 0; + + fwd.listen_host = fwd.connect_host = NULL; + if (buffer_get_int_ret(&ftype, m) != 0 || + (fwd.listen_host = buffer_get_string_ret(m, NULL)) == NULL || + buffer_get_int_ret(&fwd.listen_port, m) != 0 || + (fwd.connect_host = buffer_get_string_ret(m, NULL)) == NULL || + buffer_get_int_ret(&fwd.connect_port, m) != 0) { + error("%s: malformed message", __func__); + ret = -1; + goto out; + } + + if (*fwd.listen_host == '\0') { + xfree(fwd.listen_host); + fwd.listen_host = NULL; + } + if (*fwd.connect_host == '\0') { + xfree(fwd.connect_host); + fwd.connect_host = NULL; + } + + debug2("%s: channel %d: request %s", __func__, c->self, + (fwd_desc = format_forward(ftype, &fwd))); + + /* XXX implement this */ + buffer_put_int(r, MUX_S_FAILURE); + buffer_put_cstring(r, "unimplemented"); + + out: + if (fwd_desc != NULL) + xfree(fwd_desc); + if (fwd.listen_host != NULL) + xfree(fwd.listen_host); + if (fwd.connect_host != NULL) + xfree(fwd.connect_host); + + return ret; +} + +/* Channel callbacks fired on read/write from mux slave fd */ +static void +mux_master_read_cb(Channel *c, void *ctx) +{ + struct mux_master_state *state = (struct mux_master_state *)ctx; + Buffer in, out; + void *ptr; + u_int type, have, i; + int ret = -1; + + /* Complete setup of channel */ + if (ctx == NULL) { + state = xcalloc(1, sizeof(state)); + state->conn_state = MUX_HELLO_SEND; + c->mux_ctx = ctx = state; + channel_register_cleanup(c->self, + mux_master_control_cleanup_cb, 0); + } + +/* debug3("%s: enter channel %d ibuf len %u obuf len %u state %d", + __func__, c->self, buffer_len(&c->input), buffer_len(&c->output), + state->conn_state); */ + + switch (state->conn_state) { + case MUX_HELLO_SEND: + buffer_init(&out); + buffer_put_int(&out, MUX_MSG_HELLO); + buffer_put_int(&out, SSHMUX_VER); + /* no extensions */ + buffer_put_string(&c->output, buffer_ptr(&out), + buffer_len(&out)); + buffer_free(&out); + state->conn_state = MUX_HELLO_WAIT; + debug3("%s: channel %d: hello sent", __func__, c->self); + ret = 0; + break; + case MUX_HELLO_WAIT: + case MUX_UP: + case MUX_SESSION: + buffer_init(&in); + buffer_init(&out); + + /* Channel code ensures that we receive whole packets */ + if ((ptr = buffer_get_string_ptr_ret(&c->input, + &have)) == NULL) { + malf: + error("%s: malformed message", __func__); + goto out; + } + buffer_append(&in, ptr, have); + + if (buffer_get_int_ret(&type, &in)) + goto malf; + debug3("%s: channel %d packet type 0x%08x len %u", + __func__, c->self, type, buffer_len(&in)); + + if (state->conn_state == MUX_HELLO_WAIT && + type != MUX_MSG_HELLO) { + error("%s: expected MUX_MSG_HELLO(0x%08x), " + "received 0x%08x", __func__, MUX_MSG_HELLO, type); + goto out; + } + + for (i = 0; mux_master_handlers[i].handler != NULL; i++) { + if (type == mux_master_handlers[i].type) { + ret = mux_master_handlers[i].handler(state, + c, &in, &out); + break; + } + } + if (mux_master_handlers[i].handler == NULL) { + error("%s: unsupported mux message 0x%08x", + __func__, type); + buffer_put_int(&out, MUX_S_FAILURE); + buffer_put_cstring(&out, "unsupported request"); + ret = 0; + } + /* Enqueue reply packet */ + if (buffer_len(&out) != 0) { + buffer_put_string(&c->output, buffer_ptr(&out), + buffer_len(&out)); + } + out: +/* debug3("%s: reply channel %d ibuf len %u obuf len %u state %d", + __func__, c->self, buffer_len(&c->input), + buffer_len(&c->output), state->conn_state); */ + + buffer_free(&in); + buffer_free(&out); + break; + default: + fatal("%s: unknown state %d", __func__, state->conn_state); + } +} + +void +mux_exit_message(Channel *c, int exitval) +{ + Buffer m; + Channel *mux_chan; + + debug3("%s: channel %d: exit message, evitval %d", __func__, c->self, + exitval); + + if ((mux_chan = channel_by_id(c->ctl_chan)) == NULL) + fatal("%s: channel %d missing mux channel %d", + __func__, c->self, c->ctl_chan); + + /* Append exit message packet to control socket output queue */ + buffer_init(&m); + buffer_put_int(&m, MUX_S_EXIT_MESSAGE); + buffer_put_int(&m, exitval); + + buffer_put_string(&mux_chan->output, buffer_ptr(&m), buffer_len(&m)); + buffer_free(&m); +} + +/* Prepare a mux master to listen on a Unix domain socket. */ +void +muxserver_listen(void) +{ + struct sockaddr_un addr; + mode_t old_umask; + + 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.sun_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"); + + if ((muxserver_sock = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) + fatal("%s socket(): %s", __func__, strerror(errno)); + + old_umask = umask(0177); + if (bind(muxserver_sock, (struct sockaddr *)&addr, addr.sun_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)); } + umask(old_umask); - if (stdin_null_flag) { - if ((fd = open(_PATH_DEVNULL, O_RDONLY)) == -1) - fatal("open(/dev/null): %s", strerror(errno)); - if (dup2(fd, STDIN_FILENO) == -1) - fatal("dup2: %s", strerror(errno)); - if (fd > STDERR_FILENO) - close(fd); + if (listen(muxserver_sock, 64) == -1) + fatal("%s listen(): %s", __func__, strerror(errno)); + + set_nonblock(muxserver_sock); + + mux_listener_channel = channel_new("mux listener", + SSH_CHANNEL_MUX_LISTENER, muxserver_sock, muxserver_sock, -1, + CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, + 0, addr.sun_path, 1); + mux_listener_channel->mux_rcb = mux_master_read_cb; + debug3("%s: mux listener channel %d fd %d", __func__, + mux_listener_channel->self, mux_listener_channel->sock); +} + +/* Callback on open confirmation in mux master for a mux client session. */ +static void +mux_session_confirm(int id, void *arg) +{ + 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_by_id(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 */ } - term = getenv("TERM"); + if (cctx->want_agent_fwd && options.forward_agent) { + debug("Requesting authentication agent forwarding."); + channel_request_start(id, "auth-agent-req at openssh.com", 0); + packet_send(); + } - flags = 0; - if (tty_flag) - flags |= SSHMUX_FLAG_TTY; - if (subsystem_flag) - flags |= SSHMUX_FLAG_SUBSYS; - if (options.forward_x11) - flags |= SSHMUX_FLAG_X11_FWD; - if (options.forward_agent) - flags |= SSHMUX_FLAG_AGENT_FWD; + client_session2_setup(id, cctx->want_tty, cctx->want_subsys, + cctx->term, &cctx->tio, c->rfd, &cctx->cmd, cctx->env); - signal(SIGPIPE, SIG_IGN); + 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); + } + xfree(cctx); +} + +/* ** Multiplexing client support */ + +/* Exit signal handler */ +static void +control_client_sighandler(int signo) +{ + muxclient_terminate = signo; +} + +/* + * Relay signal handler - used to pass some signals from mux client to + * mux master. + */ +static void +control_client_sigrelay(int signo) +{ + int save_errno = errno; + + if (muxserver_pid > 1) + kill(muxserver_pid, signo); + + errno = save_errno; +} + +static int +mux_client_read(int fd, Buffer *b, u_int need) +{ + u_int have; + ssize_t len; + u_char *p; + struct pollfd pfd; + + pfd.fd = fd; + pfd.events = POLLIN; + p = buffer_append_space(b, need); + for (have = 0; have < need; ) { + if (muxclient_terminate) { + errno = EINTR; + return -1; + } + len = read(fd, p + have, need - have); + if (len < 0) { + switch (errno) { + case EAGAIN: + (void)poll(&pfd, 1, -1); + /* FALLTHROUGH */ + case EINTR: + continue; + default: + return -1; + } + } + if (len == 0) { + errno = EPIPE; + return -1; + } + have += (u_int)len; + } + return 0; +} + +static int +mux_client_write_packet(int fd, Buffer *m) +{ + Buffer queue; + u_int have, need; + int oerrno, len; + u_char *ptr; + struct pollfd pfd; + + pfd.fd = fd; + pfd.events = POLLOUT; + buffer_init(&queue); + buffer_put_string(&queue, buffer_ptr(m), buffer_len(m)); + + need = buffer_len(&queue); + ptr = buffer_ptr(&queue); + + for (have = 0; have < need; ) { + if (muxclient_terminate) { + buffer_free(&queue); + errno = EINTR; + return -1; + } + len = write(fd, ptr + have, need - have); + if (len < 0) { + switch (errno) { + case EAGAIN: + (void)poll(&pfd, 1, -1); + /* FALLTHROUGH */ + case EINTR: + continue; + default: + oerrno = errno; + buffer_free(&queue); + errno = oerrno; + return -1; + } + } + if (len == 0) { + buffer_free(&queue); + errno = EPIPE; + return -1; + } + have += (u_int)len; + } + buffer_free(&queue); + return 0; +} + +static int +mux_client_read_packet(int fd, Buffer *m) +{ + Buffer queue; + u_int need, have; + void *ptr; + int oerrno; + + buffer_init(&queue); + if (mux_client_read(fd, &queue, 4) != 0) { + if ((oerrno = errno) == EPIPE) + debug3("%s: read header failed: %s", __func__, strerror(errno)); + errno = oerrno; + return -1; + } + need = get_u32(buffer_ptr(&queue)); + if (mux_client_read(fd, &queue, need) != 0) { + oerrno = errno; + debug3("%s: read body failed: %s", __func__, strerror(errno)); + errno = oerrno; + return -1; + } + ptr = buffer_get_string_ptr(&queue, &have); + buffer_append(m, ptr, have); + buffer_free(&queue); + return 0; +} + +static int +mux_client_hello_exchange(int fd) +{ + Buffer m; + u_int type, ver; buffer_init(&m); + buffer_put_int(&m, MUX_MSG_HELLO); + buffer_put_int(&m, SSHMUX_VER); + /* no extensions */ - /* Send our command to server */ - buffer_put_int(&m, muxclient_command); - buffer_put_int(&m, flags); - if (ssh_msg_send(sock, SSHMUX_VER, &m) == -1) { - error("%s: msg_send", __func__); - muxerr: - close(sock); + if (mux_client_write_packet(fd, &m) != 0) + fatal("%s: write packet: %s", __func__, strerror(errno)); + + buffer_clear(&m); + + /* Read their HELLO */ + if (mux_client_read_packet(fd, &m) != 0) { buffer_free(&m); - if (muxclient_command != SSHMUX_COMMAND_OPEN) - cleanup_exit(255); - logit("Falling back to non-multiplexed connection"); - xfree(options.control_path); - options.control_path = NULL; - options.control_master = SSHCTL_MASTER_NO; - return; + return -1; + } + + type = buffer_get_int(&m); + if (type != MUX_MSG_HELLO) + fatal("%s: expected HELLO (%u) received %u", + __func__, MUX_MSG_HELLO, type); + ver = buffer_get_int(&m); + if (ver != SSHMUX_VER) + fatal("Unsupported multiplexing protocol version %d " + "(expected %d)", ver, SSHMUX_VER); + debug2("%s: master version %u", __func__, ver); + /* No extensions are presently defined */ + while (buffer_len(&m) > 0) { + char *name = buffer_get_string(&m, NULL); + char *value = buffer_get_string(&m, NULL); + + debug2("Unrecognised master extension \"%s\"", name); + xfree(name); + xfree(value); } + buffer_free(&m); + return 0; +} + +static u_int +mux_client_request_alive(int fd) +{ + Buffer m; + char *e; + u_int pid, type; + + debug3("%s: entering", __func__); + + buffer_init(&m); + buffer_put_int(&m, MUX_C_ALIVE_CHECK); + + if (mux_client_write_packet(fd, &m) != 0) + fatal("%s: write packet: %s", __func__, strerror(errno)); + buffer_clear(&m); - /* Get authorisation status and PID of controlee */ - if (ssh_msg_recv(sock, &m) == -1) { - error("%s: Did not receive reply from master", __func__); - goto muxerr; - } - if (buffer_get_char(&m) != SSHMUX_VER) { - error("%s: Master replied with wrong version", __func__); - goto muxerr; - } - if (buffer_get_int_ret(&allowed, &m) != 0) { - error("%s: bad server reply", __func__); - goto muxerr; - } - if (allowed != 1) { - error("Connection to master denied"); - goto muxerr; + /* Read their reply */ + if (mux_client_read_packet(fd, &m) != 0) { + buffer_free(&m); + return 0; + } + + type = buffer_get_int(&m); + if (type != MUX_S_ALIVE) { + e = buffer_get_string(&m, NULL); + fatal("%s: master returned error: %s", __func__, e); } - muxserver_pid = buffer_get_int(&m); + + pid = buffer_get_int(&m); + buffer_free(&m); + + debug3("%s: done pid = %u", __func__, pid); + + return pid; +} + +static void +mux_client_request_terminate(int fd) +{ + Buffer m; + char *e; + u_int type; + + debug3("%s: entering", __func__); + + buffer_init(&m); + buffer_put_int(&m, MUX_C_TERMINATE); + + if (mux_client_write_packet(fd, &m) != 0) + fatal("%s: write packet: %s", __func__, strerror(errno)); buffer_clear(&m); - switch (muxclient_command) { - case SSHMUX_COMMAND_ALIVE_CHECK: - fprintf(stderr, "Master running (pid=%d)\r\n", - muxserver_pid); - exit(0); - case SSHMUX_COMMAND_TERMINATE: - fprintf(stderr, "Exit request sent.\r\n"); - exit(0); - case SSHMUX_COMMAND_OPEN: - buffer_put_cstring(&m, term ? term : ""); - if (options.escape_char == SSH_ESCAPECHAR_NONE) - buffer_put_int(&m, 0xffffffff); - else - buffer_put_int(&m, options.escape_char); - buffer_append(&command, "\0", 1); - buffer_put_cstring(&m, buffer_ptr(&command)); - - if (options.num_send_env == 0 || environ == NULL) { - buffer_put_int(&m, 0); - } else { - /* Pass environment */ - num_env = 0; - for (i = 0; environ[i] != NULL; i++) { - if (env_permitted(environ[i])) - num_env++; /* Count */ - } - buffer_put_int(&m, num_env); - for (i = 0; environ[i] != NULL && num_env >= 0; i++) { - if (env_permitted(environ[i])) { - num_env--; - buffer_put_cstring(&m, environ[i]); - } - } + /* Read their reply */ + if (mux_client_read_packet(fd, &m) != 0) { + /* Remote end exited already */ + if (errno == EPIPE) { + buffer_free(&m); + return; } + fatal("%s: read from master failed: %s", + __func__, strerror(errno)); + } + + type = buffer_get_int(&m); + switch (type) { + case MUX_S_OK: break; + case MUX_S_PERMISSION_DENIED: + e = buffer_get_string(&m, NULL); + fatal("Master refused termination request: %s", e); + case MUX_S_FAILURE: + e = buffer_get_string(&m, NULL); + fatal("%s: termination request failed: %s", __func__, e); default: - fatal("unrecognised muxclient_command %d", muxclient_command); + fatal("%s: unexpected response from master 0x%08x", + __func__, type); + } + buffer_free(&m); +} + +static int +mux_client_request_forward(int fd, u_int ftype, Forward *fwd) +{ + Buffer m; + char *e, *fwd_desc; + u_int type; + + fwd_desc = format_forward(ftype, fwd); + debug("Requesting %s", fwd_desc); + xfree(fwd_desc); + + buffer_init(&m); + buffer_put_int(&m, MUX_C_OPEN_FORWARD); + buffer_put_int(&m, ftype); + buffer_put_cstring(&m, + fwd->listen_host == NULL ? "" : fwd->listen_host); + buffer_put_int(&m, fwd->listen_port); + buffer_put_cstring(&m, + fwd->connect_host == NULL ? "" : fwd->connect_host); + buffer_put_int(&m, fwd->connect_port); + + if (mux_client_write_packet(fd, &m) != 0) + fatal("%s: write packet: %s", __func__, strerror(errno)); + + buffer_clear(&m); + + /* Read their reply */ + if (mux_client_read_packet(fd, &m) != 0) { + buffer_free(&m); + return -1; } - if (ssh_msg_send(sock, SSHMUX_VER, &m) == -1) { - error("%s: msg_send", __func__); - goto muxerr; + type = buffer_get_int(&m); + switch (type) { + case MUX_S_OK: + break; + case MUX_S_PERMISSION_DENIED: + e = buffer_get_string(&m, NULL); + buffer_free(&m); + error("Master refused forwarding request: %s", e); + return -1; + case MUX_S_FAILURE: + e = buffer_get_string(&m, NULL); + buffer_free(&m); + error("%s: termination request failed: %s", __func__, e); + return -1; + default: + fatal("%s: unexpected response from master 0x%08x", + __func__, type); } + buffer_free(&m); + + return 0; +} + +static int +mux_client_request_forwards(int fd) +{ + int i; - if (mm_send_fd(sock, STDIN_FILENO) == -1 || - mm_send_fd(sock, STDOUT_FILENO) == -1 || - mm_send_fd(sock, STDERR_FILENO) == -1) { - error("%s: send fds failed", __func__); - goto muxerr; + debug3("%s: requesting forwardings: %d local, %d remote", __func__, + options.num_local_forwards, options.num_remote_forwards); + + /* XXX ExitOnForwardingFailure */ + for (i = 0; i < options.num_local_forwards; i++) { + if (mux_client_request_forward(fd, + options.local_forwards[i].connect_port == 0 ? + MUX_FWD_DYNAMIC : MUX_FWD_LOCAL, + options.local_forwards + i) != 0) + return -1; + } + for (i = 0; i < options.num_remote_forwards; i++) { + if (mux_client_request_forward(fd, MUX_FWD_REMOTE, + options.remote_forwards + i) != 0) + return -1; } + return 0; +} - /* - * Mux errors are non-recoverable from this point as the master - * has ownership of the session now. - */ +static int +mux_client_request_session(int fd) +{ + Buffer m; + char *e, *term; + u_int i, exitval, type, exitval_seen; + extern char **environ; + int devnull; + + debug3("%s: entering", __func__); + + if ((muxserver_pid = mux_client_request_alive(fd)) == 0) { + error("%s: master alive request failed", __func__); + return -1; + } - /* Wait for reply, so master has a chance to gather ttymodes */ + signal(SIGPIPE, SIG_IGN); + + if (stdin_null_flag) { + if ((devnull = open(_PATH_DEVNULL, O_RDONLY)) == -1) + fatal("open(/dev/null): %s", strerror(errno)); + if (dup2(devnull, STDIN_FILENO) == -1) + fatal("dup2: %s", strerror(errno)); + if (devnull > STDERR_FILENO) + close(devnull); + } + + term = getenv("TERM"); + + buffer_init(&m); + buffer_put_int(&m, MUX_C_NEW_SESSION); + buffer_put_int(&m, tty_flag); + buffer_put_int(&m, options.forward_x11); + buffer_put_int(&m, options.forward_agent); + buffer_put_int(&m, subsystem_flag); + buffer_put_int(&m, options.escape_char == SSH_ESCAPECHAR_NONE ? + 0xffffffff : (u_int)options.escape_char); + buffer_put_cstring(&m, term == NULL ? "" : term); + buffer_put_string(&m, buffer_ptr(&command), buffer_len(&command)); + + if (options.num_send_env > 0 && environ != NULL) { + /* Pass environment */ + for (i = 0; environ[i] != NULL; i++) { + if (env_permitted(environ[i])) { + buffer_put_cstring(&m, environ[i]); + } + } + } + + if (mux_client_write_packet(fd, &m) != 0) + fatal("%s: write packet: %s", __func__, strerror(errno)); + + /* Send the stdio file descriptors */ + if (mm_send_fd(fd, STDIN_FILENO) == -1 || + mm_send_fd(fd, STDOUT_FILENO) == -1 || + mm_send_fd(fd, STDERR_FILENO) == -1) + fatal("%s: send fds failed", __func__); + + debug3("%s: session request sent", __func__); + + /* Read their reply */ buffer_clear(&m); - if (ssh_msg_recv(sock, &m) == -1) - fatal("%s: msg_recv", __func__); - if (buffer_get_char(&m) != SSHMUX_VER) - fatal("%s: wrong version", __func__); - buffer_free(&m); + if (mux_client_read_packet(fd, &m) != 0) { + error("%s: read from master failed: %s", + __func__, strerror(errno)); + buffer_free(&m); + return -1; + } + + type = buffer_get_int(&m); + switch (type) { + case MUX_S_OK: + break; + case MUX_S_PERMISSION_DENIED: + e = buffer_get_string(&m, NULL); + buffer_free(&m); + error("Master refused forwarding request: %s", e); + return -1; + case MUX_S_FAILURE: + e = buffer_get_string(&m, NULL); + buffer_free(&m); + error("%s: termination request failed: %s", __func__, e); + return -1; + default: + buffer_free(&m); + error("%s: unexpected response from master 0x%08x", + __func__, type); + return -1; + } signal(SIGHUP, control_client_sighandler); signal(SIGINT, control_client_sighandler); @@ -676,42 +1356,118 @@ muxclient(const char *path) /* * Stick around until the controlee closes the client_fd. - * Before it does, it is expected to write this process' exit - * value (one int). This process must read the value and wait for - * the closure of the client_fd; if this one closes early, the - * multiplex master will terminate early too (possibly losing data). + * Before it does, it is expected to write an exit message. + * This process must read the value and wait for the closure of + * the client_fd; if this one closes early, the multiplex master will + * terminate early too (possibly losing data). */ - exitval[0] = 0; - for (i = 0; !muxclient_terminate && i < (int)sizeof(exitval);) { - r = read(sock, (char *)exitval + i, sizeof(exitval) - i); - if (r == 0) { - debug2("Received EOF from master"); + for (exitval = 255, exitval_seen = 0;;) { + buffer_clear(&m); + if (mux_client_read_packet(fd, &m) != 0) break; + type = buffer_get_int(&m); + if (type != MUX_S_EXIT_MESSAGE) { + e = buffer_get_string(&m, NULL); + fatal("%s: master returned error: %s", __func__, e); } - if (r == -1) { - if (errno == EINTR) - continue; - fatal("%s: read %s", __func__, strerror(errno)); - } - i += r; + if (exitval_seen) + fatal("%s: exitval sent twice", __func__); + exitval = buffer_get_int(&m); + exitval_seen = 1; } - close(sock); + close(fd); leave_raw_mode(force_tty_flag); - if (i > (int)sizeof(int)) - fatal("%s: master returned too much data (%d > %lu)", - __func__, i, (u_long)sizeof(int)); + if (muxclient_terminate) { debug2("Exiting on signal %d", muxclient_terminate); - exitval[0] = 255; - } else if (i < (int)sizeof(int)) { + exitval = 255; + } else if (!exitval_seen) { debug2("Control master terminated unexpectedly"); - exitval[0] = 255; + exitval = 255; } else - debug2("Received exit status from master %d", exitval[0]); + debug2("Received exit status from master %d", exitval); if (tty_flag && options.log_level != SYSLOG_LEVEL_QUIET) fprintf(stderr, "Shared connection to %s closed.\r\n", host); - exit(exitval[0]); + exit(exitval); +} + +/* Multiplex client main loop. */ +void +muxclient(const char *path) +{ + struct sockaddr_un addr; + int sock; + u_int pid; + + if (muxclient_command == 0) + muxclient_command = SSHMUX_COMMAND_OPEN; + + switch (options.control_master) { + case SSHCTL_MASTER_AUTO: + case SSHCTL_MASTER_AUTO_ASK: + debug("auto-mux: Trying existing master"); + /* FALLTHROUGH */ + case SSHCTL_MASTER_NO: + break; + default: + return; + } + + memset(&addr, '\0', sizeof(addr)); + addr.sun_family = AF_UNIX; + addr.sun_len = offsetof(struct sockaddr_un, sun_path) + + strlen(path) + 1; + + if (strlcpy(addr.sun_path, path, + sizeof(addr.sun_path)) >= sizeof(addr.sun_path)) + fatal("ControlPath too long"); + + if ((sock = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) + fatal("%s socket(): %s", __func__, strerror(errno)); + + if (connect(sock, (struct sockaddr *)&addr, addr.sun_len) == -1) { + if (muxclient_command != SSHMUX_COMMAND_OPEN) { + fatal("Control socket connect(%.100s): %s", path, + strerror(errno)); + } + if (errno == ENOENT) + debug("Control socket \"%.100s\" does not exist", path); + else { + error("Control socket connect(%.100s): %s", path, + strerror(errno)); + } + close(sock); + return; + } + set_nonblock(sock); + + if (mux_client_hello_exchange(sock) != 0) { + error("%s: master hello exchange failed", __func__); + close(sock); + return; + } + + switch (muxclient_command) { + case SSHMUX_COMMAND_ALIVE_CHECK: + if ((pid = mux_client_request_alive(sock)) == 0) + fatal("%s: master alive check failed", __func__); + fprintf(stderr, "Master running (pid=%d)\r\n", pid); + exit(0); + case SSHMUX_COMMAND_TERMINATE: + mux_client_request_terminate(sock); + fprintf(stderr, "Exit request sent.\r\n"); + exit(0); + case SSHMUX_COMMAND_OPEN: + if (mux_client_request_forwards(sock) != 0) { + error("%s: master forward request failed", __func__); + return; + } + mux_client_request_session(sock); + return; + default: + fatal("unrecognised muxclient_command %d", muxclient_command); + } } Index: nchan.c =================================================================== RCS file: /cvs/src/usr.bin/ssh/nchan.c,v retrieving revision 1.62 diff -u -p -r1.62 nchan.c --- nchan.c 7 Nov 2008 18:50:18 -0000 1.62 +++ nchan.c 14 Jan 2010 03:07:49 -0000 @@ -159,7 +159,7 @@ chan_ibuf_empty(Channel *c) switch (c->istate) { case CHAN_INPUT_WAIT_DRAIN: if (compat20) { - if (!(c->flags & CHAN_CLOSE_SENT)) + if (!(c->flags & (CHAN_CLOSE_SENT|CHAN_LOCAL))) chan_send_eof2(c); chan_set_istate(c, CHAN_INPUT_CLOSED); } else { @@ -276,9 +276,12 @@ static void chan_rcvd_close2(Channel *c) { debug2("channel %d: rcvd close", c->self); - if (c->flags & CHAN_CLOSE_RCVD) - error("channel %d: protocol error: close rcvd twice", c->self); - c->flags |= CHAN_CLOSE_RCVD; + if (!(c->flags & CHAN_LOCAL)) { + if (c->flags & CHAN_CLOSE_RCVD) + error("channel %d: protocol error: close rcvd twice", + c->self); + c->flags |= CHAN_CLOSE_RCVD; + } if (c->type == SSH_CHANNEL_LARVAL) { /* tear down larval channels immediately */ chan_set_ostate(c, CHAN_OUTPUT_CLOSED); @@ -300,11 +303,13 @@ chan_rcvd_close2(Channel *c) chan_set_istate(c, CHAN_INPUT_CLOSED); break; case CHAN_INPUT_WAIT_DRAIN: - chan_send_eof2(c); + if (!(c->flags & CHAN_LOCAL)) + chan_send_eof2(c); chan_set_istate(c, CHAN_INPUT_CLOSED); break; } } + void chan_rcvd_eow(Channel *c) { @@ -452,6 +457,10 @@ chan_is_dead(Channel *c, int do_send) c->self, c->efd, buffer_len(&c->extended)); return 0; } + if (c->flags & CHAN_LOCAL) { + debug2("channel %d: is dead (local)", c->self); + return 1; + } if (!(c->flags & CHAN_CLOSE_SENT)) { if (do_send) { chan_send_close2(c); -------------- next part -------------- Index: PROTOCOL.mux =================================================================== RCS file: PROTOCOL.mux diff -N PROTOCOL.mux --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ PROTOCOL.mux 14 Jan 2010 03:15:36 -0000 @@ -0,0 +1,152 @@ +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) + +This document describes the multiplexing protocol used by ssh(1)'s +ControlMaster connection-sharing. + +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 + 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 server will then reply with MUX_S_OK, 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 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 + +The server replies with: + + uint32 MUX_S_ALIVE + uint32 server pid + +4. Remotely terminating a master + +A client may request that a master terminate immediately: + + uint32 MUX_C_TERMINATE + +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 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. + +5. Requesting closure of port forwards + +A client may request the master to establish a port forward: + + uint32 MUX_C_OPEN_FORWARD + 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. Status messages + +The MUX_S_OK message is empty: + + uint32 MUX_S_OK + +The MUX_S_PERMISSION_DENIED and MUX_S_FAILURE include a reason: + + uint32 MUX_S_PERMISSION_DENIED + string reason + + uint32 MUX_S_FAILURE + 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_FWD_LOCAL 1 +#define MUX_FWD_REMOTE 2 +#define MUX_FWD_DYNAMIC 3 + +$OpenBSD$ Index: channels.c =================================================================== RCS file: /var/cvs/openssh/channels.c,v retrieving revision 1.291 diff -u -p -r1.291 channels.c --- channels.c 12 Jan 2010 08:40:27 -0000 1.291 +++ channels.c 14 Jan 2010 03:15:36 -0000 @@ -239,7 +239,6 @@ channel_register_fds(Channel *c, int rfd 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; @@ -328,6 +327,9 @@ channel_new(char *ctype, int type, int r 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->delayed = 1; /* prevent call to channel_post handler */ TAILQ_INIT(&c->status_confirms); debug("channel %d: new [%s]", found, remote_name); @@ -370,11 +372,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); @@ -400,8 +401,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); @@ -523,6 +522,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: @@ -536,6 +536,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: @@ -567,6 +568,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: @@ -617,6 +620,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: @@ -627,12 +632,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: @@ -839,9 +844,6 @@ channel_pre_open(Channel *c, fd_set *rea 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 */ @@ -986,6 +988,26 @@ channel_pre_x11_open(Channel *c, fd_set } } +static void +channel_pre_mux_client(Channel *c, fd_set *readset, fd_set *writeset) +{ + if (c->istate == CHAN_INPUT_OPEN && + buffer_check_alloc(&c->input, CHAN_RBUF)) + FD_SET(c->rfd, readset); + if (c->istate == CHAN_INPUT_WAIT_DRAIN) { + /* clear buffer immediately - partial packet */ + buffer_clear(&c->input); + chan_ibuf_empty(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 @@ -1749,36 +1771,6 @@ channel_handle_efd(Channel *c, fd_set *r 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) { @@ -1809,10 +1801,132 @@ channel_post_open(Channel *c, fd_set *re 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; + +/* debug3("%s: channel %d: entering, need %u have %u", + __func__, c->self, need, buffer_len(&c->input)); */ + 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 && + errno != EWOULDBLOCK) { + 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); + } +/* debug3("%s: channel %d: done, need %u have %u", + __func__, c->self, need, buffer_len(&c->input)); */ + 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 && 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; + c->mux_rcb(c, c->mux_ctx); + } + + 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 || + errno == EWOULDBLOCK)) + 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)) { + 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, NULL); + /* 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) @@ -1841,6 +1955,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; @@ -1849,6 +1965,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 Index: channels.h =================================================================== RCS file: /var/cvs/openssh/channels.h,v retrieving revision 1.95 diff -u -p -r1.95 channels.h --- channels.h 12 Jan 2010 08:40:27 -0000 1.95 +++ channels.h 14 Jan 2010 03:15:36 -0000 @@ -53,7 +53,9 @@ #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; @@ -81,6 +83,9 @@ struct channel_connect { struct addrinfo *ai, *aitop; }; +/* Callbacks for mux channels back into client-specific code */ +typedef void mux_callback_fn(struct Channel *, void *); + struct Channel { int type; /* channel type/state */ int self; /* my own channel identifier */ @@ -92,7 +97,7 @@ 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 */ @@ -142,6 +147,10 @@ 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; }; #define CHAN_EXTENDED_IGNORE 0 @@ -172,6 +181,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 Index: clientloop.c =================================================================== RCS file: /var/cvs/openssh/clientloop.c,v retrieving revision 1.205 diff -u -p -r1.205 clientloop.c --- clientloop.c 9 Jan 2010 11:26:23 -0000 1.205 +++ clientloop.c 14 Jan 2010 03:15:36 -0000 @@ -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 @@ -146,7 +146,7 @@ static volatile sig_atomic_t received_si static int in_non_blocking_mode = 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. */ @@ -564,9 +564,6 @@ client_wait_until_can_do_something(fd_se 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 @@ -695,7 +692,7 @@ client_status_confirm(int type, Channel /* 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", @@ -839,6 +836,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 */ @@ -936,7 +934,7 @@ process_escapes(Channel *c, Buffer *bin, 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; @@ -946,7 +944,7 @@ process_escapes(Channel *c, Buffer *bin, 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 " @@ -991,7 +989,7 @@ process_escapes(Channel *c, Buffer *bin, continue; case '&': - if (c && c->ctl_fd != -1) + if (c && c->ctl_chan != -1) goto noescape; /* * Detach the program (continue to serve @@ -1042,7 +1040,7 @@ process_escapes(Channel *c, Buffer *bin, 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\ @@ -1091,7 +1089,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; @@ -1327,8 +1325,6 @@ client_loop(int have_pty, int escape_cha 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 */ @@ -1446,12 +1442,6 @@ client_loop(int have_pty, int escape_cha /* 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; @@ -1859,9 +1849,8 @@ client_input_channel_req(int type, u_int chan_rcvd_eow(c); } else if (strcmp(rtype, "exit-status") == 0) { exitval = packet_get_int(); - if (c->ctl_fd != -1) { - /* Dispatch to mux client */ - atomicio(vwrite, c->ctl_fd, &exitval, sizeof(exitval)); + if (c->ctl_chan != -1) { + mux_exit_message(c, exitval); success = 1; } else if (id == session_ident) { /* Record exit value of local session */ Index: clientloop.h =================================================================== RCS file: /var/cvs/openssh/clientloop.h,v retrieving revision 1.22 diff -u -p -r1.22 clientloop.h --- clientloop.h 12 Jun 2008 18:55:46 -0000 1.22 +++ clientloop.h 14 Jan 2010 03:15:36 -0000 @@ -56,7 +56,7 @@ typedef void global_confirm_cb(int, u_in 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 */ @@ -71,3 +71,4 @@ void client_register_global_confirm(glo void muxserver_listen(void); int muxserver_accept_control(void); void muxclient(const char *); +void mux_exit_message(Channel *, int); Index: monitor_fdpass.c =================================================================== RCS file: /var/cvs/openssh/monitor_fdpass.c,v retrieving revision 1.30 diff -u -p -r1.30 monitor_fdpass.c --- monitor_fdpass.c 12 Jan 2010 23:54:46 -0000 1.30 +++ monitor_fdpass.c 14 Jan 2010 03:15:36 -0000 @@ -36,6 +36,10 @@ #include #ifdef HAVE_POLL_H #include +#else +# ifdef HAVE_SYS_POLL_H +# include +# endif #endif #include #include @@ -82,7 +86,7 @@ mm_send_fd(int sock, int fd) pfd.fd = sock; pfd.events = POLLOUT; while ((n = sendmsg(sock, &msg, 0)) == -1 && - (errno == EAGAIN || errno == EINTR)) { + (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK)) { debug3("%s: sendmsg(%d): %s", __func__, fd, strerror(errno)); (void)poll(&pfd, 1, -1); } @@ -138,7 +142,7 @@ mm_receive_fd(int sock) pfd.fd = sock; pfd.events = POLLIN; while ((n = recvmsg(sock, &msg, 0)) == -1 && - (errno == EAGAIN || errno == EINTR)) { + (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK)) { debug3("%s: recvmsg: %s", __func__, strerror(errno)); (void)poll(&pfd, 1, -1); } Index: mux.c =================================================================== RCS file: /var/cvs/openssh/mux.c,v retrieving revision 1.11 diff -u -p -r1.11 mux.c --- mux.c 9 Jan 2010 11:26:23 -0000 1.11 +++ mux.c 14 Jan 2010 03:15:36 -0000 @@ -17,25 +17,24 @@ /* ssh session multiplexing support */ -#include "includes.h" +// XXX signal of slave passed to master /* * 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 (after #3 obviously) + * - Maybe extension mechanisms for multi-X11/multi-agent forwarding + * - Document the mux mini-protocol somewhere. + * - 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 #include #include @@ -44,6 +43,13 @@ #include #include +#ifdef HAVE_POLL_H +#include +#else +# ifdef HAVE_SYS_POLL_H +# include +# endif +#endif #include #include #include @@ -51,6 +57,7 @@ #include #include #include +#include #ifdef HAVE_PATHS_H #include #endif @@ -64,6 +71,7 @@ #endif #include "openbsd-compat/sys-queue.h" +#include "atomicio.h" #include "xmalloc.h" #include "log.h" #include "ssh.h" @@ -88,13 +96,14 @@ extern int stdin_null_flag; extern char *host; extern int subsystem_flag; extern Buffer command; +extern volatile sig_atomic_t quit_pending; /* 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; @@ -113,269 +122,234 @@ static volatile sig_atomic_t muxclient_t /* 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; +struct mux_master_state { + enum { MUX_HELLO_SEND, MUX_HELLO_WAIT, MUX_UP, MUX_SESSION } conn_state; +}; - if (strlcpy(addr.sun_path, options.control_path, - sizeof(addr.sun_path)) >= sizeof(addr.sun_path)) - fatal("ControlPath too long"); +/* 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_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 + +/* type codes for MUX_C_OPEN_FORWARD and MUX_C_CLOSE_FORWARD */ +#define MUX_FWD_LOCAL 1 +#define MUX_FWD_REMOTE 2 +#define MUX_FWD_DYNAMIC 3 + +static void mux_session_confirm(int, void *); + +static int process_mux_master_hello(struct mux_master_state *, Channel *, + Buffer *, Buffer *); +static int process_mux_new_session(struct mux_master_state *, Channel *, + Buffer *, Buffer *); +static int process_mux_alive_check(struct mux_master_state *, Channel *, + Buffer *, Buffer *); +static int process_mux_terminate(struct mux_master_state *, Channel *, + Buffer *, Buffer *); +static int process_mux_open_forward(struct mux_master_state *, Channel *, + Buffer *, Buffer *); +static int process_mux_close_forward(struct mux_master_state *, Channel *, + Buffer *, Buffer *); + +static const struct { + u_int type; + int (*handler)(struct mux_master_state *, 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_FORWARD, process_mux_open_forward }, + { MUX_C_CLOSE_FORWARD, process_mux_close_forward }, + { 0, NULL } +}; - if ((muxserver_sock = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) - fatal("%s socket(): %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); - 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)); + 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 at openssh.com", 0); - packet_send(); - } + Channel *sc, *c = channel_by_id(cid); - 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); + 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) + debug2("%s: channel %d n session channel %d", + __func__, c->self, c->remote_id); + c->remote_id = -1; + sc->ctl_chan = -1; + chan_mark_dead(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; - } - if ((ver = buffer_get_char(&m)) != SSHMUX_VER) { - error("%s: wrong client version %d", __func__, ver); - buffer_free(&m); - close(client_fd); - return 0; - } + return 0; +} - allowed = 1; - mux_command = buffer_get_int(&m); - flags = buffer_get_int(&m); +/* Mux master protocol message handlers */ - buffer_clear(&m); +static int +process_mux_master_hello(struct mux_master_state *state, Channel *c, + Buffer *m, Buffer *r) +{ + u_int ver; - 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 (state->conn_state != MUX_HELLO_WAIT) { + error("%s: MUX_MSG_HELLO received in state MUX_UP", __func__); + return -1; + } + 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); + + /* 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); + + 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->conn_state = MUX_UP; + return 0; +} - /* 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; +static int +process_mux_new_session(struct mux_master_state *state, Channel *c, + Buffer *m, Buffer *r) +{ + Channel *nc; + struct mux_session_confirm_ctx *cctx; + char *cmd, *cp; + u_int i, j, len, env_len, escape_char, window, packetmax; + int new_fd[3]; + + if (state->conn_state != MUX_UP) { + error("%s: incorrect state %u (expected %u)", + __func__, state->conn_state, MUX_UP); + return -1; } - if (!allowed) { - error("Refused control connection"); - close(client_fd); - buffer_free(&m); - return 0; + /* Reply for SSHMUX_COMMAND_OPEN */ + cctx = xcalloc(1, sizeof(*cctx)); + cctx->term = NULL; + cmd = NULL; + if (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 (cctx->term != NULL) + xfree(cctx->term); + error("%s: malformed message", __func__); + return -1; } - 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; + 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; + } } - 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); + 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); - 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); /* 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++) @@ -386,38 +360,44 @@ muxserver_accept_control(void) xfree(cctx->env); xfree(cctx->term); buffer_free(&cctx->cmd); - close(client_fd); xfree(cctx); - return 0; + + /* prepare reply */ + buffer_put_int(r, MUX_S_FAILURE); + buffer_put_cstring(r, + "did not receive file descriptors"); + return -1; } } - debug2("%s: got fds stdin %d, stdout %d, stderr %d", __func__, + debug3("%s: got fds stdin %d, stdout %d, stderr %d", __func__, new_fd[0], new_fd[1], new_fd[2]); + if (options.control_master == SSHCTL_MASTER_ASK || + options.control_master == SSHCTL_MASTER_AUTO_ASK) { + if (!ask_permission("Allow shared connection to %s? ", host)) { + debug2("%s: session refused by user", __func__); + close(new_fd[0]); + close(new_fd[1]); + close(new_fd[2]); + xfree(cctx->term); + if (env_len != 0) { + for (i = 0; i < env_len; i++) + xfree(cctx->env[i]); + xfree(cctx->env); + } + buffer_free(&cctx->cmd); + /* prepare reply */ + buffer_put_int(r, MUX_S_PERMISSION_DENIED); + buffer_put_cstring(r, "Permission denied"); + return 0; + } + } + /* Try to pick up ttymodes from client before it goes raw */ if (cctx->want_tty && tcgetattr(new_fd[0], &cctx->tio) == -1) error("%s: tcgetattr: %s", __func__, strerror(errno)); - /* This roundtrip is just for synchronisation of ttymodes */ - buffer_clear(&m); - if (ssh_msg_send(client_fd, SSHMUX_VER, &m) == -1) { - error("%s: client msg_send failed", __func__); - close(client_fd); - close(new_fd[0]); - close(new_fd[1]); - close(new_fd[2]); - buffer_free(&m); - xfree(cctx->term); - if (env_len != 0) { - for (i = 0; i < env_len; i++) - xfree(cctx->env[i]); - xfree(cctx->env); - } - return 0; - } - buffer_free(&m); - /* enable nonblocking unless tty */ if (!isatty(new_fd[0])) set_nonblock(new_fd[0]); @@ -426,257 +406,970 @@ muxserver_accept_control(void) if (!isatty(new_fd[2])) set_nonblock(new_fd[2]); - set_nonblock(client_fd); - window = CHAN_SES_WINDOW_DEFAULT; packetmax = CHAN_SES_PACKET_DEFAULT; if (cctx->want_tty) { window >>= 1; packetmax >>= 1; } - - c = channel_new("session", SSH_CHANNEL_OPENING, + + nc = channel_new("session", SSH_CHANNEL_OPENING, new_fd[0], new_fd[1], new_fd[2], window, packetmax, CHAN_EXTENDED_WRITE, "client-session", /*nonblock*/0); - c->ctl_fd = client_fd; + nc->ctl_chan = c->self; /* link session -> control channel */ + c->remote_id = nc->self; /* link control -> session channel */ + if (cctx->want_tty && escape_char != 0xffffffff) { - channel_register_filter(c->self, + channel_register_filter(nc->self, client_simple_escape_filter, NULL, client_filter_cleanup, client_new_escape_filter_ctx((int)escape_char)); } - debug3("%s: channel_new: %d", __func__, c->self); - - channel_send_open(c->self); - channel_register_open_confirm(c->self, mux_session_confirm, cctx); - return 0; -} + debug2("%s: channel_new: %d linked to control channel %d", + __func__, nc->self, nc->ctl_chan); -/* ** Multiplexing client support */ + channel_send_open(nc->self); + channel_register_open_confirm(nc->self, mux_session_confirm, cctx); + channel_register_cleanup(nc->self, mux_master_session_cleanup_cb, 0); + + /* prepare reply */ + /* XXX defer until mux_session_confirm() fires */ + buffer_put_int(r, MUX_S_OK); + state->conn_state = MUX_SESSION; -/* Exit signal handler */ -static void -control_client_sighandler(int signo) -{ - muxclient_terminate = signo; + return 0; } -/* - * Relay signal handler - used to pass some signals from mux client to - * mux master. - */ -static void -control_client_sigrelay(int signo) +static int +process_mux_alive_check(struct mux_master_state *state, Channel *c, + Buffer *m, Buffer *r) { - int save_errno = errno; + debug2("%s: channel %d: alive check", __func__, c->self); - if (muxserver_pid > 1) - kill(muxserver_pid, signo); + /* prepare reply */ + buffer_put_int(r, MUX_S_ALIVE); + buffer_put_int(r, (u_int)getpid()); - errno = save_errno; + return 0; } -/* Check mux client environment variables before passing them to mux master. */ static int -env_permitted(char *env) +process_mux_terminate(struct mux_master_state *state, Channel *c, + Buffer *m, Buffer *r) { - int i, ret; - char name[1024], *cp; + debug2("%s: channel %d: terminate request", __func__, c->self); - if ((cp = strchr(env, '=')) == NULL || cp == env) - return (0); - ret = snprintf(name, sizeof(name), "%.*s", (int)(cp - env), env); - if (ret <= 0 || (size_t)ret >= sizeof(name)) - fatal("env_permitted: name '%.100s...' too long", env); - - for (i = 0; i < options.num_send_env; i++) - if (match_pattern(name, options.send_env[i])) - return (1); + if (options.control_master == SSHCTL_MASTER_ASK || + options.control_master == SSHCTL_MASTER_AUTO_ASK) { + if (!ask_permission("Terminate shared connection to %s? ", + host)) { + debug2("%s: termination refused by user", __func__); + buffer_put_int(r, MUX_S_PERMISSION_DENIED); + buffer_put_cstring(r, "Permission denied"); + return 0; + } + } - return (0); + quit_pending = 1; + buffer_put_int(r, MUX_S_OK); + /* XXX exit happens too soon - message never makes it to client */ + return 0; } -/* Multiplex client main loop. */ -void -muxclient(const char *path) +static char * +format_forward(u_int ftype, Forward *fwd) { - struct sockaddr_un addr; - int i, r, fd, sock, exitval[2], num_env, addr_len; - Buffer m; - char *term; - extern char **environ; - u_int allowed, flags; + char *ret; - if (muxclient_command == 0) - muxclient_command = SSHMUX_COMMAND_OPEN; - - switch (options.control_master) { - case SSHCTL_MASTER_AUTO: - case SSHCTL_MASTER_AUTO_ASK: - debug("auto-mux: Trying existing master"); - /* FALLTHROUGH */ - case SSHCTL_MASTER_NO: + switch (ftype) { + case MUX_FWD_LOCAL: + xasprintf(&ret, "local forward %.200s:%d -> %.200s:%d", + (fwd->listen_host == NULL) ? + (options.gateway_ports ? "*" : "LOCALHOST") : + fwd->listen_host, fwd->listen_port, + fwd->connect_host, fwd->connect_port); + break; + case MUX_FWD_DYNAMIC: + xasprintf(&ret, "dynamic forward %.200s:%d -> *", + (fwd->listen_host == NULL) ? + (options.gateway_ports ? "*" : "LOCALHOST") : + fwd->listen_host, fwd->listen_port); + break; + case MUX_FWD_REMOTE: + xasprintf(&ret, "remote forward %.200s:%d -> %.200s:%d", + (fwd->listen_host == NULL) ? + "LOCALHOST" : fwd->listen_host, + fwd->listen_port, + fwd->connect_host, fwd->connect_port); break; default: - return; + fatal("%s: unknown forward type %u", __func__, ftype); } + return ret; +} - memset(&addr, '\0', sizeof(addr)); - addr.sun_family = AF_UNIX; - addr_len = offsetof(struct sockaddr_un, sun_path) + - strlen(path) + 1; +static int +compare_host(const char *a, const char *b) +{ + if (a == NULL && b == NULL) + return 1; + if (a == NULL || b == NULL) + return 0; + return strcmp(a, b) == 0; +} - if (strlcpy(addr.sun_path, path, - sizeof(addr.sun_path)) >= sizeof(addr.sun_path)) - fatal("ControlPath too long"); +static int +compare_forward(Forward *a, Forward *b) +{ + if (!compare_host(a->listen_host, b->listen_host)) + return 0; + if (a->listen_port != b->listen_port) + return 0; + if (!compare_host(a->connect_host, b->connect_host)) + return 0; + if (a->connect_port != b->connect_port) + return 0; - if ((sock = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) - fatal("%s socket(): %s", __func__, strerror(errno)); + return 1; +} - if (connect(sock, (struct sockaddr *)&addr, addr_len) == -1) { - if (muxclient_command != SSHMUX_COMMAND_OPEN) { - fatal("Control socket connect(%.100s): %s", path, - strerror(errno)); +static int +process_mux_open_forward(struct mux_master_state *state, Channel *c, + Buffer *m, Buffer *r) +{ + Forward fwd; + char *fwd_desc = NULL; + u_int ftype; + int i, ret = 0, freefwd = 1; + + fwd.listen_host = fwd.connect_host = NULL; + if (buffer_get_int_ret(&ftype, m) != 0 || + (fwd.listen_host = buffer_get_string_ret(m, NULL)) == NULL || + buffer_get_int_ret(&fwd.listen_port, m) != 0 || + (fwd.connect_host = buffer_get_string_ret(m, NULL)) == NULL || + buffer_get_int_ret(&fwd.connect_port, m) != 0) { + error("%s: malformed message", __func__); + ret = -1; + goto out; + } + + if (*fwd.listen_host == '\0') { + xfree(fwd.listen_host); + fwd.listen_host = NULL; + } + if (*fwd.connect_host == '\0') { + xfree(fwd.connect_host); + fwd.connect_host = NULL; + } + + debug2("%s: channel %d: request %s", __func__, c->self, + (fwd_desc = format_forward(ftype, &fwd))); + + if (ftype != MUX_FWD_LOCAL && ftype != MUX_FWD_REMOTE && + ftype != MUX_FWD_DYNAMIC) { + logit("%s: invalid forwarding type %u", __func__, ftype); + invalid: + xfree(fwd.listen_host); + xfree(fwd.connect_host); + buffer_put_int(r, MUX_S_FAILURE); + buffer_put_cstring(r, "Invalid forwarding request"); + return 0; + } + /* XXX support rport0 forwarding with reply of port assigned */ + if (fwd.listen_port == 0 || fwd.listen_port >= 65536) { + logit("%s: invalid listen port %u", __func__, + fwd.listen_port); + goto invalid; + } + if (fwd.connect_port >= 65536 || (ftype != MUX_FWD_DYNAMIC && + ftype != MUX_FWD_REMOTE && fwd.connect_port == 0)) { + logit("%s: invalid connect port %u", __func__, + fwd.connect_port); + goto invalid; + } + if (ftype != MUX_FWD_DYNAMIC && fwd.connect_host == NULL) { + logit("%s: missing connect host", __func__); + goto invalid; + } + + /* Skip forwards that have already been requested */ + switch (ftype) { + case MUX_FWD_LOCAL: + case MUX_FWD_DYNAMIC: + for (i = 0; i < options.num_local_forwards; i++) { + if (compare_forward(&fwd, + options.local_forwards + i)) { + exists: + debug2("%s: found existing forwarding", + __func__); + buffer_put_int(r, MUX_S_OK); + goto out; + } } - if (errno == ENOENT) - debug("Control socket \"%.100s\" does not exist", path); - else { - error("Control socket connect(%.100s): %s", path, - strerror(errno)); + break; + case MUX_FWD_REMOTE: + for (i = 0; i < options.num_remote_forwards; i++) { + if (compare_forward(&fwd, + options.remote_forwards + i)) + goto exists; } - close(sock); + break; + } + + if (options.control_master == SSHCTL_MASTER_ASK || + options.control_master == SSHCTL_MASTER_AUTO_ASK) { + if (!ask_permission("Open %s on %s?", fwd_desc, host)) { + debug2("%s: forwarding refused by user", __func__); + buffer_put_int(r, MUX_S_PERMISSION_DENIED); + buffer_put_cstring(r, "Permission denied"); + goto out; + } + } + + if (ftype == MUX_FWD_LOCAL || ftype == MUX_FWD_DYNAMIC) { + if (options.num_local_forwards + 1 >= + SSH_MAX_FORWARDS_PER_DIRECTION || + channel_setup_local_fwd_listener(fwd.listen_host, + fwd.listen_port, fwd.connect_host, fwd.connect_port, + options.gateway_ports) < 0) { + fail: + logit("slave-requested %s failed", fwd_desc); + buffer_put_int(r, MUX_S_FAILURE); + buffer_put_cstring(r, "Port forwarding failed"); + goto out; + } + add_local_forward(&options, &fwd); + freefwd = 0; + } else { + /* XXX wait for remote to confirm */ + if (options.num_remote_forwards + 1 >= + SSH_MAX_FORWARDS_PER_DIRECTION || + channel_request_remote_forwarding(fwd.listen_host, + fwd.listen_port, fwd.connect_host, fwd.connect_port) < 0) + goto fail; + add_remote_forward(&options, &fwd); + freefwd = 0; + } + buffer_put_int(r, MUX_S_OK); + out: + if (fwd_desc != NULL) + xfree(fwd_desc); + if (freefwd) { + if (fwd.listen_host != NULL) + xfree(fwd.listen_host); + if (fwd.connect_host != NULL) + xfree(fwd.connect_host); + } + return ret; +} + +static int +process_mux_close_forward(struct mux_master_state *state, Channel *c, + Buffer *m, Buffer *r) +{ + Forward fwd; + char *fwd_desc = NULL; + u_int ftype; + int ret = 0; + + fwd.listen_host = fwd.connect_host = NULL; + if (buffer_get_int_ret(&ftype, m) != 0 || + (fwd.listen_host = buffer_get_string_ret(m, NULL)) == NULL || + buffer_get_int_ret(&fwd.listen_port, m) != 0 || + (fwd.connect_host = buffer_get_string_ret(m, NULL)) == NULL || + buffer_get_int_ret(&fwd.connect_port, m) != 0) { + error("%s: malformed message", __func__); + ret = -1; + goto out; + } + + if (*fwd.listen_host == '\0') { + xfree(fwd.listen_host); + fwd.listen_host = NULL; + } + if (*fwd.connect_host == '\0') { + xfree(fwd.connect_host); + fwd.connect_host = NULL; + } + + debug2("%s: channel %d: request %s", __func__, c->self, + (fwd_desc = format_forward(ftype, &fwd))); + + /* XXX implement this */ + buffer_put_int(r, MUX_S_FAILURE); + buffer_put_cstring(r, "unimplemented"); + + out: + if (fwd_desc != NULL) + xfree(fwd_desc); + if (fwd.listen_host != NULL) + xfree(fwd.listen_host); + if (fwd.connect_host != NULL) + xfree(fwd.connect_host); + + return ret; +} + +/* Channel callbacks fired on read/write from mux slave fd */ +static void +mux_master_read_cb(Channel *c, void *ctx) +{ + struct mux_master_state *state = (struct mux_master_state *)ctx; + Buffer in, out; + void *ptr; + u_int type, have, i; + int ret = -1; + + /* Complete setup of channel */ + if (ctx == NULL) { + state = xcalloc(1, sizeof(state)); + state->conn_state = MUX_HELLO_SEND; + c->mux_ctx = ctx = state; + channel_register_cleanup(c->self, + mux_master_control_cleanup_cb, 0); + } + +/* debug3("%s: enter channel %d ibuf len %u obuf len %u state %d", + __func__, c->self, buffer_len(&c->input), buffer_len(&c->output), + state->conn_state); */ + + switch (state->conn_state) { + case MUX_HELLO_SEND: + buffer_init(&out); + buffer_put_int(&out, MUX_MSG_HELLO); + buffer_put_int(&out, SSHMUX_VER); + /* no extensions */ + buffer_put_string(&c->output, buffer_ptr(&out), + buffer_len(&out)); + buffer_free(&out); + state->conn_state = MUX_HELLO_WAIT; + debug3("%s: channel %d: hello sent", __func__, c->self); + ret = 0; + break; + case MUX_HELLO_WAIT: + case MUX_UP: + case MUX_SESSION: + buffer_init(&in); + buffer_init(&out); + + /* Channel code ensures that we receive whole packets */ + if ((ptr = buffer_get_string_ptr_ret(&c->input, + &have)) == NULL) { + malf: + error("%s: malformed message", __func__); + goto out; + } + buffer_append(&in, ptr, have); + + if (buffer_get_int_ret(&type, &in)) + goto malf; + debug3("%s: channel %d packet type 0x%08x len %u", + __func__, c->self, type, buffer_len(&in)); + + if (state->conn_state == MUX_HELLO_WAIT && + type != MUX_MSG_HELLO) { + error("%s: expected MUX_MSG_HELLO(0x%08x), " + "received 0x%08x", __func__, MUX_MSG_HELLO, type); + goto out; + } + + for (i = 0; mux_master_handlers[i].handler != NULL; i++) { + if (type == mux_master_handlers[i].type) { + ret = mux_master_handlers[i].handler(state, + c, &in, &out); + break; + } + } + if (mux_master_handlers[i].handler == NULL) { + error("%s: unsupported mux message 0x%08x", + __func__, type); + buffer_put_int(&out, MUX_S_FAILURE); + buffer_put_cstring(&out, "unsupported request"); + ret = 0; + } + /* Enqueue reply packet */ + if (buffer_len(&out) != 0) { + buffer_put_string(&c->output, buffer_ptr(&out), + buffer_len(&out)); + } + out: +/* debug3("%s: reply channel %d ibuf len %u obuf len %u state %d", + __func__, c->self, buffer_len(&c->input), + buffer_len(&c->output), state->conn_state); */ + + buffer_free(&in); + buffer_free(&out); + break; + default: + fatal("%s: unknown state %d", __func__, state->conn_state); + } +} + +void +mux_exit_message(Channel *c, int exitval) +{ + Buffer m; + Channel *mux_chan; + + debug3("%s: channel %d: exit message, evitval %d", __func__, c->self, + exitval); + + if ((mux_chan = channel_by_id(c->ctl_chan)) == NULL) + fatal("%s: channel %d missing mux channel %d", + __func__, c->self, c->ctl_chan); + + /* Append exit message packet to control socket output queue */ + buffer_init(&m); + buffer_put_int(&m, MUX_S_EXIT_MESSAGE); + buffer_put_int(&m, exitval); + + buffer_put_string(&mux_chan->output, buffer_ptr(&m), buffer_len(&m)); + buffer_free(&m); +} + +/* 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"); + + if ((muxserver_sock = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) + fatal("%s socket(): %s", __func__, strerror(errno)); + + 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)); } + umask(old_umask); - if (stdin_null_flag) { - if ((fd = open(_PATH_DEVNULL, O_RDONLY)) == -1) - fatal("open(/dev/null): %s", strerror(errno)); - if (dup2(fd, STDIN_FILENO) == -1) - fatal("dup2: %s", strerror(errno)); - if (fd > STDERR_FILENO) - close(fd); + if (listen(muxserver_sock, 64) == -1) + fatal("%s listen(): %s", __func__, strerror(errno)); + + set_nonblock(muxserver_sock); + + mux_listener_channel = channel_new("mux listener", + SSH_CHANNEL_MUX_LISTENER, muxserver_sock, muxserver_sock, -1, + CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, + 0, addr.sun_path, 1); + mux_listener_channel->mux_rcb = mux_master_read_cb; + debug3("%s: mux listener channel %d fd %d", __func__, + mux_listener_channel->self, mux_listener_channel->sock); +} + +/* Callback on open confirmation in mux master for a mux client session. */ +static void +mux_session_confirm(int id, void *arg) +{ + 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_by_id(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 */ } - term = getenv("TERM"); + if (cctx->want_agent_fwd && options.forward_agent) { + debug("Requesting authentication agent forwarding."); + channel_request_start(id, "auth-agent-req at openssh.com", 0); + packet_send(); + } - flags = 0; - if (tty_flag) - flags |= SSHMUX_FLAG_TTY; - if (subsystem_flag) - flags |= SSHMUX_FLAG_SUBSYS; - if (options.forward_x11) - flags |= SSHMUX_FLAG_X11_FWD; - if (options.forward_agent) - flags |= SSHMUX_FLAG_AGENT_FWD; + client_session2_setup(id, cctx->want_tty, cctx->want_subsys, + cctx->term, &cctx->tio, c->rfd, &cctx->cmd, cctx->env); - signal(SIGPIPE, SIG_IGN); + 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); + } + xfree(cctx); +} + +/* ** Multiplexing client support */ + +/* Exit signal handler */ +static void +control_client_sighandler(int signo) +{ + muxclient_terminate = signo; +} + +/* + * Relay signal handler - used to pass some signals from mux client to + * mux master. + */ +static void +control_client_sigrelay(int signo) +{ + int save_errno = errno; + + if (muxserver_pid > 1) + kill(muxserver_pid, signo); + + errno = save_errno; +} + +static int +mux_client_read(int fd, Buffer *b, u_int need) +{ + u_int have; + ssize_t len; + u_char *p; + struct pollfd pfd; + + pfd.fd = fd; + pfd.events = POLLIN; + p = buffer_append_space(b, need); + for (have = 0; have < need; ) { + if (muxclient_terminate) { + errno = EINTR; + return -1; + } + len = read(fd, p + have, need - have); + if (len < 0) { + switch (errno) { + case EAGAIN: +#if EAGAIN != EWOULDBLOCK + case EWOULDBLOCK: +#endif + (void)poll(&pfd, 1, -1); + /* FALLTHROUGH */ + case EINTR: + continue; + default: + return -1; + } + } + if (len == 0) { + errno = EPIPE; + return -1; + } + have += (u_int)len; + } + return 0; +} + +static int +mux_client_write_packet(int fd, Buffer *m) +{ + Buffer queue; + u_int have, need; + int oerrno, len; + u_char *ptr; + struct pollfd pfd; + + pfd.fd = fd; + pfd.events = POLLOUT; + buffer_init(&queue); + buffer_put_string(&queue, buffer_ptr(m), buffer_len(m)); + + need = buffer_len(&queue); + ptr = buffer_ptr(&queue); + + for (have = 0; have < need; ) { + if (muxclient_terminate) { + buffer_free(&queue); + errno = EINTR; + return -1; + } + len = write(fd, ptr + have, need - have); + if (len < 0) { + switch (errno) { + case EAGAIN: +#if EAGAIN != EWOULDBLOCK + case EWOULDBLOCK: +#endif + (void)poll(&pfd, 1, -1); + /* FALLTHROUGH */ + case EINTR: + continue; + default: + oerrno = errno; + buffer_free(&queue); + errno = oerrno; + return -1; + } + } + if (len == 0) { + buffer_free(&queue); + errno = EPIPE; + return -1; + } + have += (u_int)len; + } + buffer_free(&queue); + return 0; +} + +static int +mux_client_read_packet(int fd, Buffer *m) +{ + Buffer queue; + u_int need, have; + void *ptr; + int oerrno; + + buffer_init(&queue); + if (mux_client_read(fd, &queue, 4) != 0) { + if ((oerrno = errno) == EPIPE) + debug3("%s: read header failed: %s", __func__, strerror(errno)); + errno = oerrno; + return -1; + } + need = get_u32(buffer_ptr(&queue)); + if (mux_client_read(fd, &queue, need) != 0) { + oerrno = errno; + debug3("%s: read body failed: %s", __func__, strerror(errno)); + errno = oerrno; + return -1; + } + ptr = buffer_get_string_ptr(&queue, &have); + buffer_append(m, ptr, have); + buffer_free(&queue); + return 0; +} + +static int +mux_client_hello_exchange(int fd) +{ + Buffer m; + u_int type, ver; buffer_init(&m); + buffer_put_int(&m, MUX_MSG_HELLO); + buffer_put_int(&m, SSHMUX_VER); + /* no extensions */ - /* Send our command to server */ - buffer_put_int(&m, muxclient_command); - buffer_put_int(&m, flags); - if (ssh_msg_send(sock, SSHMUX_VER, &m) == -1) { - error("%s: msg_send", __func__); - muxerr: - close(sock); + if (mux_client_write_packet(fd, &m) != 0) + fatal("%s: write packet: %s", __func__, strerror(errno)); + + buffer_clear(&m); + + /* Read their HELLO */ + if (mux_client_read_packet(fd, &m) != 0) { buffer_free(&m); - if (muxclient_command != SSHMUX_COMMAND_OPEN) - cleanup_exit(255); - logit("Falling back to non-multiplexed connection"); - xfree(options.control_path); - options.control_path = NULL; - options.control_master = SSHCTL_MASTER_NO; - return; + return -1; + } + + type = buffer_get_int(&m); + if (type != MUX_MSG_HELLO) + fatal("%s: expected HELLO (%u) received %u", + __func__, MUX_MSG_HELLO, type); + ver = buffer_get_int(&m); + if (ver != SSHMUX_VER) + fatal("Unsupported multiplexing protocol version %d " + "(expected %d)", ver, SSHMUX_VER); + debug2("%s: master version %u", __func__, ver); + /* No extensions are presently defined */ + while (buffer_len(&m) > 0) { + char *name = buffer_get_string(&m, NULL); + char *value = buffer_get_string(&m, NULL); + + debug2("Unrecognised master extension \"%s\"", name); + xfree(name); + xfree(value); } + buffer_free(&m); + return 0; +} + +static u_int +mux_client_request_alive(int fd) +{ + Buffer m; + char *e; + u_int pid, type; + + debug3("%s: entering", __func__); + + buffer_init(&m); + buffer_put_int(&m, MUX_C_ALIVE_CHECK); + + if (mux_client_write_packet(fd, &m) != 0) + fatal("%s: write packet: %s", __func__, strerror(errno)); + buffer_clear(&m); - /* Get authorisation status and PID of controlee */ - if (ssh_msg_recv(sock, &m) == -1) { - error("%s: Did not receive reply from master", __func__); - goto muxerr; - } - if (buffer_get_char(&m) != SSHMUX_VER) { - error("%s: Master replied with wrong version", __func__); - goto muxerr; - } - if (buffer_get_int_ret(&allowed, &m) != 0) { - error("%s: bad server reply", __func__); - goto muxerr; - } - if (allowed != 1) { - error("Connection to master denied"); - goto muxerr; + /* Read their reply */ + if (mux_client_read_packet(fd, &m) != 0) { + buffer_free(&m); + return 0; } - muxserver_pid = buffer_get_int(&m); + + type = buffer_get_int(&m); + if (type != MUX_S_ALIVE) { + e = buffer_get_string(&m, NULL); + fatal("%s: master returned error: %s", __func__, e); + } + + pid = buffer_get_int(&m); + buffer_free(&m); + + debug3("%s: done pid = %u", __func__, pid); + + return pid; +} + +static void +mux_client_request_terminate(int fd) +{ + Buffer m; + char *e; + u_int type; + + debug3("%s: entering", __func__); + + buffer_init(&m); + buffer_put_int(&m, MUX_C_TERMINATE); + + if (mux_client_write_packet(fd, &m) != 0) + fatal("%s: write packet: %s", __func__, strerror(errno)); buffer_clear(&m); - switch (muxclient_command) { - case SSHMUX_COMMAND_ALIVE_CHECK: - fprintf(stderr, "Master running (pid=%d)\r\n", - muxserver_pid); - exit(0); - case SSHMUX_COMMAND_TERMINATE: - fprintf(stderr, "Exit request sent.\r\n"); - exit(0); - case SSHMUX_COMMAND_OPEN: - buffer_put_cstring(&m, term ? term : ""); - if (options.escape_char == SSH_ESCAPECHAR_NONE) - buffer_put_int(&m, 0xffffffff); - else - buffer_put_int(&m, options.escape_char); - buffer_append(&command, "\0", 1); - buffer_put_cstring(&m, buffer_ptr(&command)); - - if (options.num_send_env == 0 || environ == NULL) { - buffer_put_int(&m, 0); - } else { - /* Pass environment */ - num_env = 0; - for (i = 0; environ[i] != NULL; i++) { - if (env_permitted(environ[i])) - num_env++; /* Count */ - } - buffer_put_int(&m, num_env); - for (i = 0; environ[i] != NULL && num_env >= 0; i++) { - if (env_permitted(environ[i])) { - num_env--; - buffer_put_cstring(&m, environ[i]); - } - } + /* Read their reply */ + if (mux_client_read_packet(fd, &m) != 0) { + /* Remote end exited already */ + if (errno == EPIPE) { + buffer_free(&m); + return; } + fatal("%s: read from master failed: %s", + __func__, strerror(errno)); + } + + type = buffer_get_int(&m); + switch (type) { + case MUX_S_OK: break; + case MUX_S_PERMISSION_DENIED: + e = buffer_get_string(&m, NULL); + fatal("Master refused termination request: %s", e); + case MUX_S_FAILURE: + e = buffer_get_string(&m, NULL); + fatal("%s: termination request failed: %s", __func__, e); default: - fatal("unrecognised muxclient_command %d", muxclient_command); + fatal("%s: unexpected response from master 0x%08x", + __func__, type); } + buffer_free(&m); +} - if (ssh_msg_send(sock, SSHMUX_VER, &m) == -1) { - error("%s: msg_send", __func__); - goto muxerr; +static int +mux_client_request_forward(int fd, u_int ftype, Forward *fwd) +{ + Buffer m; + char *e, *fwd_desc; + u_int type; + + fwd_desc = format_forward(ftype, fwd); + debug("Requesting %s", fwd_desc); + xfree(fwd_desc); + + buffer_init(&m); + buffer_put_int(&m, MUX_C_OPEN_FORWARD); + buffer_put_int(&m, ftype); + buffer_put_cstring(&m, + fwd->listen_host == NULL ? "" : fwd->listen_host); + buffer_put_int(&m, fwd->listen_port); + buffer_put_cstring(&m, + fwd->connect_host == NULL ? "" : fwd->connect_host); + buffer_put_int(&m, fwd->connect_port); + + if (mux_client_write_packet(fd, &m) != 0) + fatal("%s: write packet: %s", __func__, strerror(errno)); + + buffer_clear(&m); + + /* Read their reply */ + if (mux_client_read_packet(fd, &m) != 0) { + buffer_free(&m); + return -1; } - if (mm_send_fd(sock, STDIN_FILENO) == -1 || - mm_send_fd(sock, STDOUT_FILENO) == -1 || - mm_send_fd(sock, STDERR_FILENO) == -1) { - error("%s: send fds failed", __func__); - goto muxerr; + type = buffer_get_int(&m); + switch (type) { + case MUX_S_OK: + break; + case MUX_S_PERMISSION_DENIED: + e = buffer_get_string(&m, NULL); + buffer_free(&m); + error("Master refused forwarding request: %s", e); + return -1; + case MUX_S_FAILURE: + e = buffer_get_string(&m, NULL); + buffer_free(&m); + error("%s: termination request failed: %s", __func__, e); + return -1; + default: + fatal("%s: unexpected response from master 0x%08x", + __func__, type); } + buffer_free(&m); - /* - * Mux errors are non-recoverable from this point as the master - * has ownership of the session now. - */ + return 0; +} + +static int +mux_client_request_forwards(int fd) +{ + int i; - /* Wait for reply, so master has a chance to gather ttymodes */ + debug3("%s: requesting forwardings: %d local, %d remote", __func__, + options.num_local_forwards, options.num_remote_forwards); + + /* XXX ExitOnForwardingFailure */ + for (i = 0; i < options.num_local_forwards; i++) { + if (mux_client_request_forward(fd, + options.local_forwards[i].connect_port == 0 ? + MUX_FWD_DYNAMIC : MUX_FWD_LOCAL, + options.local_forwards + i) != 0) + return -1; + } + for (i = 0; i < options.num_remote_forwards; i++) { + if (mux_client_request_forward(fd, MUX_FWD_REMOTE, + options.remote_forwards + i) != 0) + return -1; + } + return 0; +} + +static int +mux_client_request_session(int fd) +{ + Buffer m; + char *e, *term; + u_int i, exitval, type, exitval_seen; + extern char **environ; + int devnull; + + debug3("%s: entering", __func__); + + if ((muxserver_pid = mux_client_request_alive(fd)) == 0) { + error("%s: master alive request failed", __func__); + return -1; + } + + signal(SIGPIPE, SIG_IGN); + + if (stdin_null_flag) { + if ((devnull = open(_PATH_DEVNULL, O_RDONLY)) == -1) + fatal("open(/dev/null): %s", strerror(errno)); + if (dup2(devnull, STDIN_FILENO) == -1) + fatal("dup2: %s", strerror(errno)); + if (devnull > STDERR_FILENO) + close(devnull); + } + + term = getenv("TERM"); + + buffer_init(&m); + buffer_put_int(&m, MUX_C_NEW_SESSION); + buffer_put_int(&m, tty_flag); + buffer_put_int(&m, options.forward_x11); + buffer_put_int(&m, options.forward_agent); + buffer_put_int(&m, subsystem_flag); + buffer_put_int(&m, options.escape_char == SSH_ESCAPECHAR_NONE ? + 0xffffffff : (u_int)options.escape_char); + buffer_put_cstring(&m, term == NULL ? "" : term); + buffer_put_string(&m, buffer_ptr(&command), buffer_len(&command)); + + if (options.num_send_env > 0 && environ != NULL) { + /* Pass environment */ + for (i = 0; environ[i] != NULL; i++) { + if (env_permitted(environ[i])) { + buffer_put_cstring(&m, environ[i]); + } + } + } + + if (mux_client_write_packet(fd, &m) != 0) + fatal("%s: write packet: %s", __func__, strerror(errno)); + + /* Send the stdio file descriptors */ + if (mm_send_fd(fd, STDIN_FILENO) == -1 || + mm_send_fd(fd, STDOUT_FILENO) == -1 || + mm_send_fd(fd, STDERR_FILENO) == -1) + fatal("%s: send fds failed", __func__); + + debug3("%s: session request sent", __func__); + + /* Read their reply */ buffer_clear(&m); - if (ssh_msg_recv(sock, &m) == -1) - fatal("%s: msg_recv", __func__); - if (buffer_get_char(&m) != SSHMUX_VER) - fatal("%s: wrong version", __func__); - buffer_free(&m); + if (mux_client_read_packet(fd, &m) != 0) { + error("%s: read from master failed: %s", + __func__, strerror(errno)); + buffer_free(&m); + return -1; + } + + type = buffer_get_int(&m); + switch (type) { + case MUX_S_OK: + break; + case MUX_S_PERMISSION_DENIED: + e = buffer_get_string(&m, NULL); + buffer_free(&m); + error("Master refused forwarding request: %s", e); + return -1; + case MUX_S_FAILURE: + e = buffer_get_string(&m, NULL); + buffer_free(&m); + error("%s: termination request failed: %s", __func__, e); + return -1; + default: + buffer_free(&m); + error("%s: unexpected response from master 0x%08x", + __func__, type); + return -1; + } signal(SIGHUP, control_client_sighandler); signal(SIGINT, control_client_sighandler); @@ -688,42 +1381,119 @@ muxclient(const char *path) /* * Stick around until the controlee closes the client_fd. - * Before it does, it is expected to write this process' exit - * value (one int). This process must read the value and wait for - * the closure of the client_fd; if this one closes early, the - * multiplex master will terminate early too (possibly losing data). + * Before it does, it is expected to write an exit message. + * This process must read the value and wait for the closure of + * the client_fd; if this one closes early, the multiplex master will + * terminate early too (possibly losing data). */ - exitval[0] = 0; - for (i = 0; !muxclient_terminate && i < (int)sizeof(exitval);) { - r = read(sock, (char *)exitval + i, sizeof(exitval) - i); - if (r == 0) { - debug2("Received EOF from master"); + exitval = -1; + for (exitval_seen = 0;;) { + buffer_clear(&m); + if (mux_client_read_packet(fd, &m) != 0) break; + type = buffer_get_int(&m); + if (type != MUX_S_EXIT_MESSAGE) { + e = buffer_get_string(&m, NULL); + fatal("%s: master returned error: %s", __func__, e); } - if (r == -1) { - if (errno == EINTR) - continue; - fatal("%s: read %s", __func__, strerror(errno)); - } - i += r; + if (exitval_seen) + fatal("%s: exitval sent twice", __func__); + exitval = buffer_get_int(&m); + exitval_seen = 1; } - close(sock); + close(fd); leave_raw_mode(force_tty_flag); - if (i > (int)sizeof(int)) - fatal("%s: master returned too much data (%d > %lu)", - __func__, i, (u_long)sizeof(int)); + if (muxclient_terminate) { debug2("Exiting on signal %d", muxclient_terminate); - exitval[0] = 255; - } else if (i < (int)sizeof(int)) { + exitval = 255; + } else if (!exitval_seen) { debug2("Control master terminated unexpectedly"); - exitval[0] = 255; + exitval = 255; } else - debug2("Received exit status from master %d", exitval[0]); + debug2("Received exit status from master %d", exitval); if (tty_flag && options.log_level != SYSLOG_LEVEL_QUIET) fprintf(stderr, "Shared connection to %s closed.\r\n", host); - exit(exitval[0]); + exit(exitval); +} + +/* Multiplex client main loop. */ +void +muxclient(const char *path) +{ + struct sockaddr_un addr; + int sock, addr_len; + u_int pid; + + if (muxclient_command == 0) + muxclient_command = SSHMUX_COMMAND_OPEN; + + switch (options.control_master) { + case SSHCTL_MASTER_AUTO: + case SSHCTL_MASTER_AUTO_ASK: + debug("auto-mux: Trying existing master"); + /* FALLTHROUGH */ + case SSHCTL_MASTER_NO: + break; + default: + return; + } + + memset(&addr, '\0', sizeof(addr)); + addr.sun_family = AF_UNIX; + addr_len = offsetof(struct sockaddr_un, sun_path) + + strlen(path) + 1; + + if (strlcpy(addr.sun_path, path, + sizeof(addr.sun_path)) >= sizeof(addr.sun_path)) + fatal("ControlPath too long"); + + if ((sock = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) + fatal("%s socket(): %s", __func__, strerror(errno)); + + if (connect(sock, (struct sockaddr *)&addr, addr_len) == -1) { + if (muxclient_command != SSHMUX_COMMAND_OPEN) { + fatal("Control socket connect(%.100s): %s", path, + strerror(errno)); + } + if (errno == ENOENT) + debug("Control socket \"%.100s\" does not exist", path); + else { + error("Control socket connect(%.100s): %s", path, + strerror(errno)); + } + close(sock); + return; + } + set_nonblock(sock); + + if (mux_client_hello_exchange(sock) != 0) { + error("%s: master hello exchange failed", __func__); + close(sock); + return; + } + + switch (muxclient_command) { + case SSHMUX_COMMAND_ALIVE_CHECK: + if ((pid = mux_client_request_alive(sock)) == 0) + fatal("%s: master alive check failed", __func__); + fprintf(stderr, "Master running (pid=%d)\r\n", pid); + exit(0); + case SSHMUX_COMMAND_TERMINATE: + mux_client_request_terminate(sock); + fprintf(stderr, "Exit request sent.\r\n"); + exit(0); + case SSHMUX_COMMAND_OPEN: + if (mux_client_request_forwards(sock) != 0) { + error("%s: master forward request failed", __func__); + return; + } + mux_client_request_session(sock); + return; + default: + fatal("unrecognised muxclient_command %d", muxclient_command); + } } Index: nchan.c =================================================================== RCS file: /var/cvs/openssh/nchan.c,v retrieving revision 1.61 diff -u -p -r1.61 nchan.c --- nchan.c 11 Nov 2008 05:32:25 -0000 1.61 +++ nchan.c 14 Jan 2010 03:15:36 -0000 @@ -161,7 +161,7 @@ chan_ibuf_empty(Channel *c) switch (c->istate) { case CHAN_INPUT_WAIT_DRAIN: if (compat20) { - if (!(c->flags & CHAN_CLOSE_SENT)) + if (!(c->flags & (CHAN_CLOSE_SENT|CHAN_LOCAL))) chan_send_eof2(c); chan_set_istate(c, CHAN_INPUT_CLOSED); } else { @@ -278,9 +278,12 @@ static void chan_rcvd_close2(Channel *c) { debug2("channel %d: rcvd close", c->self); - if (c->flags & CHAN_CLOSE_RCVD) - error("channel %d: protocol error: close rcvd twice", c->self); - c->flags |= CHAN_CLOSE_RCVD; + if (!(c->flags & CHAN_LOCAL)) { + if (c->flags & CHAN_CLOSE_RCVD) + error("channel %d: protocol error: close rcvd twice", + c->self); + c->flags |= CHAN_CLOSE_RCVD; + } if (c->type == SSH_CHANNEL_LARVAL) { /* tear down larval channels immediately */ chan_set_ostate(c, CHAN_OUTPUT_CLOSED); @@ -302,11 +305,13 @@ chan_rcvd_close2(Channel *c) chan_set_istate(c, CHAN_INPUT_CLOSED); break; case CHAN_INPUT_WAIT_DRAIN: - chan_send_eof2(c); + if (!(c->flags & CHAN_LOCAL)) + chan_send_eof2(c); chan_set_istate(c, CHAN_INPUT_CLOSED); break; } } + void chan_rcvd_eow(Channel *c) { @@ -454,6 +459,10 @@ chan_is_dead(Channel *c, int do_send) c->self, c->efd, buffer_len(&c->extended)); return 0; } + if (c->flags & CHAN_LOCAL) { + debug2("channel %d: is dead (local)", c->self); + return 1; + } if (!(c->flags & CHAN_CLOSE_SENT)) { if (do_send) { chan_send_close2(c); From corbu.teren at gmail.com Thu Jan 14 16:53:26 2010 From: corbu.teren at gmail.com (Teren la Mare) Date: Thu, 14 Jan 2010 00:53:26 -0500 Subject: LOTURI DE CASA (500mp) CU VEDERE LA MARE, (in rate de 200E / Luna) LANGA PLAJA CORBU / PRET - incepand de la 5500 Message-ID: <00e0c0c6-40192-8e360370910185@server> Stimate Domn (Doamna), Imi permit sa va prezint oferta noastra de teren (loturi de 500mp) in apropierea plajelor Corbu si Vadu (10 km nord de Mamaia) langa viitoarea Statiune EUROPA, terenuri cu propunere intravilan, intr-o zona superba, ce cunoaste o dezvoltare accelerata in ultimii 2 ani, deschizand posibilitati avantajoase de investitie pe termen scurt si mediu sau ofera sansa achizitionarii unei parcele pentru constructia unei case de vacanta, vila sau pensiuni turistice langa mare la un pret cu adevarat avantajos. PENTRU DETALII COMPLETE ACCESATI WWW.TERENLAMARE.RO Loturi de Casa (OFERTA LIMITATA): Suprafata - 500mp + Alee de Acces Gratuita Cadastru; Intabulare; Utilitati in zona; Propus Intravilan. 200 euro/luna - Rata Fixa Avans - 500Euro; Dobanda - 0%; Comision 0% pt Antecontract Vanzare - Cumparare (rate) Avans - 1 000euro; Dobanda - 0%; Comision 0% pt Contract Vanzare - Cumpare cu privilegiu (rate) Doar cu Cartea de Identitate; Fara alte documente, adeverinte, extrase, etc... FOARTE IMPORTANT : TOATE LOTURILE PREZENTATE SUNT CONSTRUIBILE (APROBARE DE TRECERE IN INTRAVILAN), PUTEM FACILITA OBTINEREA TUTUROR AVIZELOR PENTRU CONSTRUCTII CU O AMPRENTA LA SOL DE MAXIM 100MP SI NU MAI MULT DE 2 ETAJE. LA ACESTE PROIECTE VENIM CU SOLUTII TEHNICE CU COSTURI MINIME CE ASIGURA INDEPENDENTA FATA DE SURSELE CONVENTIONALE DE DISTRIBUTIE UTILITATI (APA, ENERGIE ELECTRICA) FOLOSIND SURSELE NATURALE. Loturile de teren pe care vi le prezentam, au cadastru si intabulare separate pe fiecare lot in parte. Aleile de acces cu latimi cuprinse intre 8 si 12 m sunt parte integranta a contractului de vanzare-cumparare constituindu-se ca proprietate in suprafata indiviza si sunt cu titlu gratuit, necomportand nici un alt cost suplimentar. Loturile si aleile prezentate sunt intarusate de cadastrist autorizat, iar in momentul cumpararii, dumneavoastra veti primi istoricul acestora. La acontare veti primi o copie a tuturor documentelor ce atesta proprietatea acestora. Vanzarea - Cumpararea fiind facuta direct cu proprietarul, nu exista nici un comision de firma . Vizitele se pot face oricand, inclusiv in week-end cu un telefon in prealabil cu cel putin 1-2 zile inainte. Toate loturile prezentate au vedere la mare si intra in zona de dezvoltare rezidentiala al carei PUZ se afla in lucru. Distanta fata de plaja este intre 500 - 900 m, cu mentiunea ca plajele unice din zona Corbu - Vadu avand latimi cuprinse intre 200 si 400 metri. Un alt avantaj major este ca exista posibilitatea ca aceste achizitii pe care le veti face pot fi achitate, datorita unui sistem de creditare propriu, cu o rata fixa de 200 Euro/luna, si un avans de 0%; dobanda 0%; comision 0% (programarea ratelor se face in momentul acontarii, de comun acord, si va fi mentionata in contractul notarial) O prezentare succinta a comunei Corbu si a planurilor de dezvoltare zonala. Mai multe informatii puteti gasi si pe site-ul nostru WWW.TERENLAMARE.RO Pentru mai multe relatii si pregatirea unei vizite in teren ma puteti contacta pe numarul de mobil mentionat mai jos. Cu respect, Gheorghiu Gabriel 0724 - 244077 Florin Tulea 0729 - 808899 OFERTE : 1) Corbu loturi in sola 292/41 de 500mp 25/20 +alee de servitude 8m (gratuita) Cadastru intabulare, apa curent si gaze la 100m. Vedere superba la mare, Pret 8 500 Euro / Lot Plata in Rate Fixe : 200Euro/luna; 42 luni. 2) Corbu Loturi in sola 268A/10; suprafata 500mp + alee de servitute 10m (gratuita) Cadastru - Intabulare , deschidere la soseaua betonata Corbu - Vadu) Vedere superba la mare, aproape de plaja Vadu. Pret 8 000 Euro / lot Plata in Rate Fixe : 200Euro/luna; 40 luni. 3) Corbu Loturi in sola 268A/26 ; suprafata 500 mp + alee de servitute 6m (gratuita) Cadastru - Intabulare , deschidere la soseaua betonata Corbu - Vadu. Vedere superba la mare , aproape de plaja Vadu Pret 7 000 Euro / lot Plata in Rate Fixe : 200Euro/luna; 35 luni. 4) Corbu Loturi in sola 289/29 ; suprafata 500 mp + alee de servitute 9m (gratuita) Cadastru - Intabulare Vedere superba la mare , aproape de plaja Vadu Pret 5 500 Euro / lot Plata in Rate Fixe : 200Euro/luna; 27 luni. 5) Corbu - Loturi in sola 272/12/8; suprafata de la 500 la 660 mp + alee de servitute 10m (gratuita). Cadastru si Intabulare, deschidere la 2 drumuri comunale; zona cuprinsa in planul Statiunii Europene. Utilitati la 100 m. Vedere superba la Mare. Pret : 13 000 Euro / Lot Plata in Rate Fixe : 200Euro/luna; 65 luni. 6) Corbu - Loturi in sola 567 / 61; suprafata de 500 mp + alee de servitute 10m (gratuita). Cadastru si Intabulare. Utilitati la 100m. Vedere superba la mare. Pret : 8 500 Euro / Lot Plata in Rate Fixe : 200Euro/luna; 42 luni. TERENURI INTRAVILANE (comuna Corbu): Teren la Drum Judetean, in centrul comunei Corbu - Suprafata de 3 600 mp Compusa din 3 loturi : Primul Lot : Suprafata de 1 300 mp (25 x 52 m) ; deschidere la DJ - 25 m Lotul nr. 2 : Suprafata de 1 300 mp (25 x 52 m) ; deschidere la DJ - 25 m Lotul nr. 3 : Suprafata de 1 000 mp (50 x 20 m ) ; deschidere - 20 m Cadastru si Intabulare Apa curenta Curent Electric : 220 V si 380 V. Posibilitate de constructie imediata. Pret : 25 Euro / metru patrat (se negociaza la mai mult de un lot cumparat) - vezi attachment (nu se vinde in rate) Loturi in Noul Cartier Rezidential format din 60 loturi a cate 300 mp cu deschidere si vedere superba la Lacul Corbu. Primul Lot : Suprafata de 300 mp (15 x 20 m) ; deschidere direct la malul lacului - 15m Pret : 15 000 Euro Lotul nr.2 : Suprafata de 300 mp (15 x 20 m) ; deschidere la prima strada de la lac - 15m Pret : 12 000 Euro Cadastru si Intabulare; Utilitati ; Loturile sunt asezate unul in prelungirea celuilalt. Cea mai buna varianta este de a se cumpara impreuna. - Vezi attachment. (nu se vinde in rate) Telefon : Fix : 0241 765443 Gabi Gheorghiu : 0724 244077 Florin Tulea : 0729 808899 E-mail: corbu.teren at gmail.com Web : WWW.TERENLAMARE.RO Daca doriti sa nu mai fiti contactat de noi pe viitor va rugam sa dati un reply acestui email cu subiectul DEZABONARE. Va multumim! Cu respect, Florin Tulea - 0729 808899 From mjmasterson at xo.com Thu Jan 14 10:03:12 2010 From: mjmasterson at xo.com (Michael Masterson) Date: Wed, 13 Jan 2010 17:03:12 -0600 Subject: Directory permissions in chroot SFTP References: 4a73f9240811110345h29e0c822i6e5922affb153185@mail.gmail.com Message-ID: <4B4E5130.1030006@xo.com> >Right, this is on purpose. We ban this because allowing a user write >access to a chroot target is dangerously similar to equivalence with >allowing write access to the root of a filesystem. Could you tell me what the *real* dangers of allowing SFTP only users to write to their directories? we've got a server with a few hundred users that we need to chroot, and would prefer not to have to change what directories they end up in, and all the users have to have their files placed in the directories by another non-root user, basically, we've got a couple hundred external customers that sftp in and either pick up, or drop off files, and an internal user process that puts the files in the directories or picks them up. changing how all those users access would take well over a year... -- MM work: 972-509-2375, mobile:469-576-1908 -------------- next part -------------- A non-text attachment was scrubbed... Name: mjmasterson.vcf Type: text/x-vcard Size: 290 bytes Desc: not available URL: From sfandino at yahoo.com Fri Jan 15 03:40:44 2010 From: sfandino at yahoo.com (Salvador Fandino) Date: Thu, 14 Jan 2010 17:40:44 +0100 Subject: ssh(1) multiplexing rewrite In-Reply-To: References: Message-ID: <4B4F490C.70006@yahoo.com> Damien Miller wrote: > Hi, > > At the n2k10 OpenBSD network hackathon, I finally got some time to clean > up and rewrite the ssh(1) client multiplexing code. The attached diffs > (one for portable OpenSSH, one for OpenBSD) are the result, and they > need some testing. I have repeatedly run the test suite for my Perl module Net::OpenSSH that (ab)uses the multiplexing feature without errors. And, while you are at it, I have some feature requests: 1) add support for sending signals to the remote processes via mux control commands. The bug tracker contains a patch by Darren Tucker implementing the signal part of the SSH protocol and later I submitted another patch (now obsoleted by your changes) to request sending the signals via mux control commands (https://bugzilla.mindrot.org/show_bug.cgi?id=1424). 2) add support for the new netcat-like feature over mux. 3) allow to run the mux server over SSH stdin instead of over a named Unix socket. That would be useful to embed ssh inside another program. For instance, Net::OpenSSH internally starts a new ssh in master mode and then sends commands to the remote machine through the mux socket running slave ssh processes, one per command. Handling the named Unix socket is a nuisance because it means accessing the file system, looking for a proper location to place the socket, checking that permissions are right, avoiding collisions with other instances of the module concurrently running and cleaning up. To add this feature to OpenSSH, besides allowing attaching the mux server to stdio, the protocol should also be modified in order to allow interleaving requests and responses related to different channels over the same mux stream, for instance including some session ID. It would also require making the mux protocol public so that it could be implemented by third party clients. From my POV, feature 1 is a must, 2 would be nice to have and 3... well, it is more on the mental masturbation side. I could put some effort on writing patches for 1 or 2 but only if there exists some possibility of getting then accepted. Cheers, - Salva From djm at mindrot.org Fri Jan 15 10:07:40 2010 From: djm at mindrot.org (Damien Miller) Date: Fri, 15 Jan 2010 10:07:40 +1100 (EST) Subject: Directory permissions in chroot SFTP In-Reply-To: <4B4E5130.1030006@xo.com> References: 4a73f9240811110345h29e0c822i6e5922affb153185@mail.gmail.com <4B4E5130.1030006@xo.com> Message-ID: On Wed, 13 Jan 2010, Michael Masterson wrote: > > Right, this is on purpose. We ban this because allowing a user write > > access to a chroot target is dangerously similar to equivalence with > > allowing write access to the root of a filesystem. > > Could you tell me what the *real* dangers of allowing SFTP only users to write > to their directories? https://bugzilla.redhat.com/show_bug.cgi?id=522141 -d From cmadams at hiwaay.net Fri Jan 15 10:48:48 2010 From: cmadams at hiwaay.net (Chris Adams) Date: Thu, 14 Jan 2010 17:48:48 -0600 Subject: Directory permissions in chroot SFTP In-Reply-To: References: <4B4E5130.1030006@xo.com> Message-ID: <20100114234848.GB1121573@hiwaay.net> Once upon a time, Damien Miller said: > On Wed, 13 Jan 2010, Michael Masterson wrote: > > > Right, this is on purpose. We ban this because allowing a user write > > > access to a chroot target is dangerously similar to equivalence with > > > allowing write access to the root of a filesystem. > > > > Could you tell me what the *real* dangers of allowing SFTP only users to write > > to their directories? > > https://bugzilla.redhat.com/show_bug.cgi?id=522141 I guess I'm missing something - how does an SFTP-only user run something? Is there another way to restrict SFTP to a user's home directory? -- Chris Adams Systems and Network Administrator - HiWAAY Internet Services I don't speak for anybody but myself - that's enough trouble. From djm at mindrot.org Fri Jan 15 14:50:18 2010 From: djm at mindrot.org (Damien Miller) Date: Fri, 15 Jan 2010 14:50:18 +1100 (EST) Subject: Directory permissions in chroot SFTP In-Reply-To: <20100114234848.GB1121573@hiwaay.net> References: <4B4E5130.1030006@xo.com> <20100114234848.GB1121573@hiwaay.net> Message-ID: On Thu, 14 Jan 2010, Chris Adams wrote: > Once upon a time, Damien Miller said: > > On Wed, 13 Jan 2010, Michael Masterson wrote: > > > > Right, this is on purpose. We ban this because allowing a user write > > > > access to a chroot target is dangerously similar to equivalence with > > > > allowing write access to the root of a filesystem. > > > > > > Could you tell me what the *real* dangers of allowing SFTP only users to write > > > to their directories? > > > > https://bugzilla.redhat.com/show_bug.cgi?id=522141 > > I guess I'm missing something - how does an SFTP-only user run > something? Server misconfiguration, bugs in sshd's unprivileged code, bugs in sftp-server. > Is there another way to restrict SFTP to a user's home directory? No, and I don't think one is necessary. If having to create a subdirectory (which users can automatically be cd'd to on sftp login) is so onerous then feel free to reintroduce CVE-2009-2904 by removing the checks in session.c:safely_chroot(). -d From mailingliste91 at gmx.de Sat Jan 16 20:14:02 2010 From: mailingliste91 at gmx.de (Christoph Bessei) Date: Sat, 16 Jan 2010 10:14:02 +0100 Subject: (kein Betreff) Message-ID: <4B51835A.5080006@gmx.de> From joachim at joachimschipper.nl Sun Jan 17 06:34:30 2010 From: joachim at joachimschipper.nl (Joachim Schipper) Date: Sat, 16 Jan 2010 20:34:30 +0100 Subject: Repost: [patch] Automatically add keys to agent In-Reply-To: <20100112002433.GC27817@polymnia.jschipper.dynalias.net> References: <20100112002433.GC27817@polymnia.jschipper.dynalias.net> Message-ID: <20100116193428.GA32743@polymnia.jschipper.dynalias.net> On Tue, Jan 12, 2010 at 01:24:34AM +0100, Joachim Schipper wrote: > My keys are secured with a passphrase. That's good for security, but > having to type the passphrase either at every login or at every > invocation of ssh(1) is annoying. > Hence, this patch. I'll just quote ssh_config(5): > AddKeyToAgent If this option is set to ``yes'' and ssh-agent(1) is running, any keys used will be added to the agent (with the default lifetime). Setting this to ``ask'' will cause ssh to require confirmation using the SSH_ASKPASS program before the key is added (see ssh-add(1) for details). The argument must be ``yes'', ``ask'', or ``no''. The default is ``no''. I am a bit disappointed by the total lack of response - does nobody else have this problem? I'm willing to do more work on it, if so desired, and I wouldn't mind having to wait until OpenBSD 4.7 is tagged if everyone's too busy right now. In short, what do I have to do to get this in, or at least a curt "no, it sucks because of "? The patch below is equivalent to the patch I posted tuesday, with the sole exception that it documents that all used keys (not just passphrased keys) are added to the agent. Which is probably less surprising anyway. Joachim Index: authfile.c =================================================================== RCS file: /usr/obsd-repos//src/usr.bin/ssh/authfile.c,v retrieving revision 1.78 diff -u -p -r1.78 authfile.c --- authfile.c 11 Jan 2010 04:46:45 -0000 1.78 +++ authfile.c 11 Jan 2010 22:35:04 -0000 @@ -552,8 +552,8 @@ key_load_private_type(int type, const ch strerror(errno)); if (perm_ok != NULL) *perm_ok = 0; - } return NULL; + } if (!key_perm_ok(fd, filename)) { if (perm_ok != NULL) *perm_ok = 0; Index: readconf.c =================================================================== RCS file: /usr/obsd-repos//src/usr.bin/ssh/readconf.c,v retrieving revision 1.182 diff -u -p -r1.182 readconf.c --- readconf.c 9 Jan 2010 23:04:13 -0000 1.182 +++ readconf.c 11 Jan 2010 22:19:10 -0000 @@ -128,7 +128,7 @@ typedef enum { oSendEnv, oControlPath, oControlMaster, oHashKnownHosts, oTunnel, oTunnelDevice, oLocalCommand, oPermitLocalCommand, oVisualHostKey, oUseRoaming, oZeroKnowledgePasswordAuthentication, - oDeprecated, oUnsupported + oAddKey, oDeprecated, oUnsupported } OpCodes; /* Textual representations of the tokens. */ @@ -232,6 +232,7 @@ static struct { #else { "zeroknowledgepasswordauthentication", oUnsupported }, #endif + { "addkeytoagent", oAddKey }, { NULL, oBadOption } }; @@ -914,6 +915,10 @@ parse_int: intptr = &options->use_roaming; goto parse_flag; + case oAddKey: + intptr = &options->add_key; + goto parse_yesnoask; + case oDeprecated: debug("%s line %d: Deprecated option \"%s\"", filename, linenum, keyword); @@ -1064,6 +1069,7 @@ initialize_options(Options * options) options->local_command = NULL; options->permit_local_command = -1; options->use_roaming = -1; + options->add_key = -1; options->visual_host_key = -1; options->zero_knowledge_password_authentication = -1; } @@ -1202,6 +1208,8 @@ fill_default_options(Options * options) options->permit_local_command = 0; if (options->use_roaming == -1) options->use_roaming = 1; + if (options->add_key == -1) + options->add_key = 0; if (options->visual_host_key == -1) options->visual_host_key = 0; if (options->zero_knowledge_password_authentication == -1) Index: readconf.h =================================================================== RCS file: /usr/obsd-repos//src/usr.bin/ssh/readconf.h,v retrieving revision 1.81 diff -u -p -r1.81 readconf.h --- readconf.h 9 Jan 2010 23:04:13 -0000 1.81 +++ readconf.h 11 Jan 2010 22:19:18 -0000 @@ -125,6 +125,8 @@ typedef struct { int use_roaming; + int add_key; /* add keys to agent */ + } Options; #define SSHCTL_MASTER_NO 0 Index: ssh-agent.1 =================================================================== RCS file: /usr/obsd-repos//src/usr.bin/ssh/ssh-agent.1,v retrieving revision 1.49 diff -u -p -r1.49 ssh-agent.1 --- ssh-agent.1 22 Oct 2009 15:02:12 -0000 1.49 +++ ssh-agent.1 11 Jan 2010 23:39:47 -0000 @@ -109,6 +109,13 @@ When the command dies, so does the agent .Pp The agent initially does not have any private keys. Keys are added using +.Xr ssh 1 +(see +.Cm AddKeyToAgent +in +.Xr ssh_config 5 +for details) +or .Xr ssh-add 1 . When executed without arguments, .Xr ssh-add 1 Index: ssh.1 =================================================================== RCS file: /usr/obsd-repos//src/usr.bin/ssh/ssh.1,v retrieving revision 1.290 diff -u -p -r1.290 ssh.1 --- ssh.1 11 Jan 2010 01:39:46 -0000 1.290 +++ ssh.1 11 Jan 2010 23:14:55 -0000 @@ -428,6 +428,7 @@ For full details of the options listed b .Xr ssh_config 5 . .Pp .Bl -tag -width Ds -offset indent -compact +.It AddKeyToAgent .It AddressFamily .It BatchMode .It BindAddress @@ -803,6 +804,10 @@ The most convenient way to use public ke authentication agent. See .Xr ssh-agent 1 +and (optionally) the +.Cm AddKeyToAgent +directive in +.Xr ssh_config 5 for more information. .Pp Challenge-response authentication works as follows: Index: ssh_config.5 =================================================================== RCS file: /usr/obsd-repos//src/usr.bin/ssh/ssh_config.5,v retrieving revision 1.126 diff -u -p -r1.126 ssh_config.5 --- ssh_config.5 9 Jan 2010 23:04:13 -0000 1.126 +++ ssh_config.5 16 Jan 2010 19:20:09 -0000 @@ -116,6 +116,27 @@ a canonicalized host name before matchin See .Sx PATTERNS for more information on patterns. +.It Cm AddKeyToAgent +If this option is set to +.Dq yes +and +.Xr ssh-agent 1 +is running, any keys used will be added to the agent (with the default +lifetime). +Setting this to +.Dq ask +will cause ssh to require confirmation using the +.Ev SSH_ASKPASS +program before the key is added (see +.Xr ssh-add 1 +for details). +The argument must be +.Dq yes , +.Dq ask , +or +.Dq no . +The default is +.Dq no . .It Cm AddressFamily Specifies which address family to use when connecting. Valid arguments are Index: sshconnect1.c =================================================================== RCS file: /usr/obsd-repos//src/usr.bin/ssh/sshconnect1.c,v retrieving revision 1.70 diff -u -p -r1.70 sshconnect1.c --- sshconnect1.c 6 Nov 2006 21:25:28 -0000 1.70 +++ sshconnect1.c 16 Jan 2010 19:16:52 -0000 @@ -57,21 +57,15 @@ extern char *__progname; * authenticate using the agent. */ static int -try_agent_authentication(void) +try_agent_authentication(AuthenticationConnection *auth) { int type; char *comment; - AuthenticationConnection *auth; u_char response[16]; u_int i; Key *key; BIGNUM *challenge; - /* Get connection to the agent. */ - auth = ssh_get_authentication_connection(); - if (!auth) - return 0; - if ((challenge = BN_new()) == NULL) fatal("try_agent_authentication: BN_new failed"); /* Loop through identities served by the agent. */ @@ -134,7 +128,6 @@ try_agent_authentication(void) /* The server returns success if it accepted the authentication. */ if (type == SSH_SMSG_SUCCESS) { - ssh_close_authentication_connection(auth); BN_clear_free(challenge); debug("RSA authentication accepted by server."); return 1; @@ -144,7 +137,6 @@ try_agent_authentication(void) packet_disconnect("Protocol error waiting RSA auth response: %d", type); } - ssh_close_authentication_connection(auth); BN_clear_free(challenge); debug("RSA authentication using agent refused."); return 0; @@ -200,7 +192,7 @@ respond_to_rsa_challenge(BIGNUM * challe * the user using it. */ static int -try_rsa_authentication(int idx) +try_rsa_authentication(int idx, AuthenticationConnection *auth) { BIGNUM *challenge; Key *public, *private; @@ -293,6 +285,19 @@ try_rsa_authentication(int idx) return 0; } + /* + * Consider adding key to agent. We add keys for the default lifetime + * with no need to confirm each use. + */ + if (auth != NULL && (options.add_key == 1 || + (options.add_key == 2 && + ask_permission("Add key %s (%s) to agent?", authfile, comment)))) { + if (ssh_add_identity_constrained(auth, private, comment, 0, 0)) + debug("Identity added: %s (%s)", authfile, comment); + else + verbose("Error while adding identity!"); + } + /* Compute and send a response to the challenge. */ respond_to_rsa_challenge(challenge, private->rsa); @@ -670,6 +675,7 @@ ssh_userauth1(const char *local_user, co Sensitive *sensitive) { int i, type; + AuthenticationConnection *auth = NULL; if (supported_authentications == 0) fatal("ssh_userauth1: server supports no auth methods"); @@ -715,14 +721,15 @@ ssh_userauth1(const char *local_user, co * agent is tried first because no passphrase is needed for * it, whereas identity files may require passphrases. */ - if (try_agent_authentication()) + auth = ssh_get_authentication_connection(); + if (auth != NULL && try_agent_authentication(auth)) goto success; /* Try RSA authentication for each identity. */ for (i = 0; i < options.num_identity_files; i++) if (options.identity_keys[i] != NULL && options.identity_keys[i]->type == KEY_RSA1 && - try_rsa_authentication(i)) + try_rsa_authentication(i, auth)) goto success; } /* Try challenge response authentication if the server supports it. */ @@ -746,5 +753,6 @@ ssh_userauth1(const char *local_user, co /* NOTREACHED */ success: - return; /* need statement after label */ + if (auth) + ssh_close_authentication_connection(auth); } Index: sshconnect2.c =================================================================== RCS file: /usr/obsd-repos//src/usr.bin/ssh/sshconnect2.c,v retrieving revision 1.178 diff -u -p -r1.178 sshconnect2.c --- sshconnect2.c 11 Jan 2010 04:46:45 -0000 1.178 +++ sshconnect2.c 11 Jan 2010 23:12:38 -0000 @@ -244,7 +244,7 @@ void userauth(Authctxt *, char *); static int sign_and_send_pubkey(Authctxt *, Identity *); static void pubkey_prepare(Authctxt *); static void pubkey_cleanup(Authctxt *); -static Key *load_identity_file(char *); +static Key *load_identity_file(char *, AuthenticationConnection *); static Authmethod *authmethod_get(char *authlist); static Authmethod *authmethod_lookup(const char *name); @@ -1102,7 +1102,7 @@ input_userauth_jpake_server_confirm(int static int identity_sign(Identity *id, u_char **sigp, u_int *lenp, - u_char *data, u_int datalen) + u_char *data, u_int datalen, AuthenticationConnection *auth) { Key *prv; int ret; @@ -1118,7 +1118,7 @@ identity_sign(Identity *id, u_char **sig if (id->isprivate || (id->key->flags & KEY_FLAG_EXT)) return (key_sign(id->key, sigp, lenp, data, datalen)); /* load the private key from the file */ - if ((prv = load_identity_file(id->filename)) == NULL) + if ((prv = load_identity_file(id->filename, auth)) == NULL) return (-1); ret = key_sign(prv, sigp, lenp, data, datalen); key_free(prv); @@ -1168,7 +1168,7 @@ sign_and_send_pubkey(Authctxt *authctxt, /* generate signature */ ret = identity_sign(id, &signature, &slen, - buffer_ptr(&b), buffer_len(&b)); + buffer_ptr(&b), buffer_len(&b), authctxt->agent); if (ret == -1) { xfree(blob); buffer_free(&b); @@ -1240,30 +1240,36 @@ send_pubkey_test(Authctxt *authctxt, Ide } static Key * -load_identity_file(char *filename) +load_identity_file(char *filename, AuthenticationConnection *ac) { Key *private; - char prompt[300], *passphrase; - int perm_ok = 0, quit, i; + char prompt[300], *passphrase, *comment = NULL; + int perm_ok = 0, quit, i, allowed = 0; struct stat st; if (stat(filename, &st) < 0) { debug3("no such identity: %s", filename); return NULL; } - private = key_load_private_type(KEY_UNSPEC, filename, "", NULL, &perm_ok); - if (!perm_ok) + private = key_load_private_type(KEY_UNSPEC, filename, "", &comment, &perm_ok); + if (!perm_ok) { + if (comment) + xfree(comment); return NULL; + } if (private == NULL) { - if (options.batch_mode) + if (options.batch_mode) { + if (comment) + xfree(comment); return NULL; + } snprintf(prompt, sizeof prompt, "Enter passphrase for key '%.100s': ", filename); for (i = 0; i < options.number_of_password_prompts; i++) { passphrase = read_passphrase(prompt, 0); if (strcmp(passphrase, "") != 0) { private = key_load_private_type(KEY_UNSPEC, - filename, passphrase, NULL, NULL); + filename, passphrase, &comment, NULL); quit = 0; } else { debug2("no passphrase given, try next key"); @@ -1273,9 +1279,39 @@ load_identity_file(char *filename) xfree(passphrase); if (private != NULL || quit) break; + if (comment) + xfree(comment); debug2("bad passphrase given, try again..."); } } + + /* If we loaded the key and have an agent, consider adding key. */ + if (private == NULL || ac == NULL) { + if (comment) + xfree(comment); + return private; + } + if (options.add_key == 1) + allowed = 1; + if (options.add_key == 2) { + if (comment == NULL) + allowed = ask_permission("Add key %s to agent?", + filename); + else + allowed = ask_permission("Add key %s (%s) to agent?", + filename, comment); + } + + if (allowed) { + /* Add for default lifetime; do not confirm each use. */ + if (ssh_add_identity_constrained(ac, private, comment, 0, 0)) + debug("Identity added: %s (%s)", filename, comment); + else + debug("Error while adding identity!"); + } + + if (comment) + xfree(comment); return private; } @@ -1394,7 +1430,8 @@ userauth_pubkey(Authctxt *authctxt) sent = send_pubkey_test(authctxt, id); } else if (id->key == NULL) { debug("Trying private key: %s", id->filename); - id->key = load_identity_file(id->filename); + id->key = load_identity_file(id->filename, + authctxt->agent); if (id->key != NULL) { id->isprivate = 1; sent = sign_and_send_pubkey(authctxt, id); From peter at stuge.se Sun Jan 17 07:19:52 2010 From: peter at stuge.se (Peter Stuge) Date: Sat, 16 Jan 2010 21:19:52 +0100 Subject: Repost: [patch] Automatically add keys to agent In-Reply-To: <20100116193428.GA32743@polymnia.jschipper.dynalias.net> References: <20100112002433.GC27817@polymnia.jschipper.dynalias.net> <20100116193428.GA32743@polymnia.jschipper.dynalias.net> Message-ID: <20100116201952.13451.qmail@stuge.se> Joachim Schipper wrote: > I am a bit disappointed by the total lack of response - does nobody else > have this problem? It's not a big problem for me. I wrote a script which is sourced from .bashrc. --8<-- .ssh/agent.sh #!/bin/bash source /etc/profile source ~/.ssh/.agent.env 2>/dev/null ssh-add -l >/dev/null 2>&1 ret=$? case $ret in 2) ssh-agent | grep '^SSH_\(AGENT_PID\|AUTH_SOCK\)=' > ~/.ssh/.agent.env source ~/.ssh/.agent.env ssh-add -c ;; 1) ssh-add -c ;; *) ;; esac -->8-- //Peter From rees at merit.edu Sun Jan 17 09:06:03 2010 From: rees at merit.edu (Jim Rees) Date: Sat, 16 Jan 2010 17:06:03 -0500 Subject: Repost: [patch] Automatically add keys to agent In-Reply-To: <20100116193428.GA32743@polymnia.jschipper.dynalias.net> References: <20100112002433.GC27817@polymnia.jschipper.dynalias.net> <20100116193428.GA32743@polymnia.jschipper.dynalias.net> Message-ID: <20100116220603.GA14449@merit.edu> Joachim Schipper wrote: I am a bit disappointed by the total lack of response - does nobody else have this problem? It's less a problem than an inconvenience for me. I would be in favor of putting this code in. From jrollins at finestructure.net Sun Jan 17 09:15:47 2010 From: jrollins at finestructure.net (Jameson Rollins) Date: Sat, 16 Jan 2010 17:15:47 -0500 Subject: Repost: [patch] Automatically add keys to agent In-Reply-To: <20100116193428.GA32743@polymnia.jschipper.dynalias.net> References: <20100112002433.GC27817@polymnia.jschipper.dynalias.net> <20100116193428.GA32743@polymnia.jschipper.dynalias.net> Message-ID: <20100116221547.GA26952@finestructure.net> On Sat, Jan 16, 2010 at 08:34:30PM +0100, Joachim Schipper wrote: > On Tue, Jan 12, 2010 at 01:24:34AM +0100, Joachim Schipper wrote: > > My keys are secured with a passphrase. That's good for security, but > > having to type the passphrase either at every login or at every > > invocation of ssh(1) is annoying. > > > Hence, this patch. I'll just quote ssh_config(5): > > > AddKeyToAgent > If this option is set to ``yes'' and ssh-agent(1) is running, any > keys used will be added to the agent (with the default lifetime). > Setting this to ``ask'' will cause ssh to require confirmation > using the SSH_ASKPASS program before the key is added (see > ssh-add(1) for details). The argument must be ``yes'', ``ask'', > or ``no''. The default is ``no''. > > I am a bit disappointed by the total lack of response - does nobody else > have this problem? I'm willing to do more work on it, if so desired, and > I wouldn't mind having to wait until OpenBSD 4.7 is tagged if everyone's > too busy right now. I think probably everyone already has hooks or wrapper scripts they've put together to accomplish this. For instance I have a proxycommand that does it for me. That said, I think it's a pretty good idea. I would rather use something like this than the hackish wrapper scripts I'm currently using. That said, I wasn't a big fan of your dismissal of the ssh-add -c option. I think that is a very important option that everyone should be using. You should always want to be informed if anything is trying to use your key. Otherwise a malicious program could gain access to your key without your knowning it. jamie. -------------- next part -------------- A non-text attachment was scrubbed... Name: not available Type: application/pgp-signature Size: 836 bytes Desc: Digital signature URL: From jcs at jcs.org Sun Jan 17 09:19:36 2010 From: jcs at jcs.org (joshua stein) Date: Sat, 16 Jan 2010 15:19:36 -0700 Subject: Repost: [patch] Automatically add keys to agent In-Reply-To: <20100116193428.GA32743@polymnia.jschipper.dynalias.net> References: <20100112002433.GC27817@polymnia.jschipper.dynalias.net> <20100116193428.GA32743@polymnia.jschipper.dynalias.net> Message-ID: <20100116221935.GI32049@humble.superblock.net> > I am a bit disappointed by the total lack of response - does nobody else > have this problem? i like it. i have xlock running "ssh-add -D" before locking the screen and "ssh-add" again after unlocking, but that requires entering the key passphrase after every unlock, even if i don't use ssh before locking again. with your patch i can just wait until i have to use ssh first before adding the key back to the agent. this is how ssh works on mac os x with its integrated keychain support. my only comment about the code itself might be to change oAddKey and add_key to something with "agent" in the name, just for clarity when reading through it later. From djm at mindrot.org Sun Jan 17 11:28:59 2010 From: djm at mindrot.org (Damien Miller) Date: Sun, 17 Jan 2010 11:28:59 +1100 (EST) Subject: Repost: [patch] Automatically add keys to agent In-Reply-To: <20100116193428.GA32743@polymnia.jschipper.dynalias.net> References: <20100112002433.GC27817@polymnia.jschipper.dynalias.net> <20100116193428.GA32743@polymnia.jschipper.dynalias.net> Message-ID: On Sat, 16 Jan 2010, Joachim Schipper wrote: > > Hence, this patch. I'll just quote ssh_config(5): > > > AddKeyToAgent > If this option is set to ``yes'' and ssh-agent(1) is running, any > keys used will be added to the agent (with the default lifetime). > Setting this to ``ask'' will cause ssh to require confirmation > using the SSH_ASKPASS program before the key is added (see > ssh-add(1) for details). The argument must be ``yes'', ``ask'', > or ``no''. The default is ``no''. > > I am a bit disappointed by the total lack of response - does nobody else > have this problem? I'm willing to do more work on it, if so desired, and > I wouldn't mind having to wait until OpenBSD 4.7 is tagged if everyone's > too busy right now. Please file it at https://bugzilla.mindrot.org/ so it doesn't get lost. We can take a look at it after OpenSSH-5.4 ships. -d From openssh at roumenpetrov.info Sun Jan 17 22:19:15 2010 From: openssh at roumenpetrov.info (Roumen Petrov) Date: Sun, 17 Jan 2010 13:19:15 +0200 Subject: Repost: [patch] Automatically add keys to agent In-Reply-To: <20100116193428.GA32743@polymnia.jschipper.dynalias.net> References: <20100112002433.GC27817@polymnia.jschipper.dynalias.net> <20100116193428.GA32743@polymnia.jschipper.dynalias.net> Message-ID: <4B52F233.6030903@roumenpetrov.info> Joachim Schipper wrote: > On Tue, Jan 12, 2010 at 01:24:34AM +0100, Joachim Schipper wrote: >> My keys are secured with a passphrase. That's good for security, but >> having to type the passphrase either at every login or at every >> invocation of ssh(1) is annoying. > >> Hence, this patch. I'll just quote ssh_config(5): >> > AddKeyToAgent > If this option is set to ``yes'' and ssh-agent(1) is running, any > keys used will be added to the agent (with the default lifetime). > Setting this to ``ask'' will cause ssh to require confirmation > using the SSH_ASKPASS program before the key is added (see > ssh-add(1) for details). The argument must be ``yes'', ``ask'', > or ``no''. The default is ``no''. > > I am a bit disappointed by the total lack of response - does nobody else > have this problem? I'm willing to do more work on it, if so desired, and > I wouldn't mind having to wait until OpenBSD 4.7 is tagged if everyone's > too busy right now. [SNIP] Why to use this as I could use IdentitiesOnly and IdentityFile per host as initially I could load all required keys info agent ? May be instead new option you could enhance existing option IdentitiesOnly with ask. Roumen From djm at mindrot.org Mon Jan 18 15:26:07 2010 From: djm at mindrot.org (Damien Miller) Date: Mon, 18 Jan 2010 15:26:07 +1100 (EST) Subject: ssh(1) multiplexing rewrite In-Reply-To: <4B4F490C.70006@yahoo.com> References: <4B4F490C.70006@yahoo.com> Message-ID: On Thu, 14 Jan 2010, Salvador Fandino wrote: > Damien Miller wrote: > > Hi, > > > > At the n2k10 OpenBSD network hackathon, I finally got some time to clean > > up and rewrite the ssh(1) client multiplexing code. The attached diffs > > (one for portable OpenSSH, one for OpenBSD) are the result, and they > > need some testing. > > I have repeatedly run the test suite for my Perl module Net::OpenSSH that > (ab)uses the multiplexing feature without errors. > > And, while you are at it, I have some feature requests: > > 1) add support for sending signals to the remote processes via mux control > commands. The bug tracker contains a patch by Darren Tucker implementing the > signal part of the SSH protocol and later I submitted another patch (now > obsoleted by your changes) to request sending the signals via mux control > commands (https://bugzilla.mindrot.org/show_bug.cgi?id=1424). If/when Darren's diff goes in then adding this should be pretty easy; the only problem I can think of is identifying which session to signal. I think we would need something like your friendly-name extension to sort it out, and that implies adding the ability to query a running mux master to discover what sessions are open and their names. This would be post-OpenSSH 5.4, so please file an enhancement request so it doesn't get lost. > 2) add support for the new netcat-like feature over mux. Yes, this was planned and now implemented. See the attached diff; OpenBSD only for now, I'll do the portability bits when it goes in. > 3) allow to run the mux server over SSH stdin instead of over a named > Unix socket. That would be useful to embed ssh inside another program. > > For instance, Net::OpenSSH internally starts a new ssh in master mode > and then sends commands to the remote machine through the mux socket > running slave ssh processes, one per command. > > Handling the named Unix socket is a nuisance because it means > accessing the file system, looking for a proper location to place the > socket, checking that permissions are right, avoiding collisions with > other instances of the module concurrently running and cleaning up. > > To add this feature to OpenSSH, besides allowing attaching the mux > server to stdio, the protocol should also be modified in order to > allow interleaving requests and responses related to different > channels over the same mux stream, for instance including some session > ID. Yes, that sounds reasonable. I'm not afraid to make incompatible changes to this protocol and adding interleaved requests isn't so hard now. Please file a bug for this too, along with any specific suggestions for how you think the protocol should work (since you have already put more thought into it than I :) > It would also require making the mux protocol public so that it could be > implemented by third party clients. That's why PROTOCOL.mux exists :) -d -------------- next part -------------- Index: channels.c =================================================================== RCS file: /cvs/src/usr.bin/ssh/channels.c,v retrieving revision 1.301 diff -u -p -r1.301 channels.c --- channels.c 11 Jan 2010 01:39:46 -0000 1.301 +++ channels.c 18 Jan 2010 04:20:34 -0000 @@ -235,7 +235,6 @@ channel_register_fds(Channel *c, int rfd 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; @@ -323,6 +322,9 @@ channel_new(char *ctype, int type, int r 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->delayed = 1; /* prevent call to channel_post handler */ TAILQ_INIT(&c->status_confirms); debug("channel %d: new [%s]", found, remote_name); @@ -365,11 +367,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); @@ -395,8 +396,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); @@ -518,6 +517,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: @@ -531,6 +531,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: @@ -562,6 +563,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: @@ -612,6 +615,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: @@ -622,12 +627,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: @@ -834,9 +839,6 @@ channel_pre_open(Channel *c, fd_set *rea 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 */ @@ -981,6 +983,28 @@ channel_pre_x11_open(Channel *c, fd_set } } +static void +channel_pre_mux_client(Channel *c, fd_set *readset, fd_set *writeset) +{ + if (c->istate == CHAN_INPUT_OPEN && + 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 @@ -1213,19 +1237,14 @@ channel_decode_socks5(Channel *c, fd_set } Channel * -channel_connect_stdio_fwd(const char *host_to_connect, u_short port_to_connect) +channel_connect_stdio_fwd(const char *host_to_connect, u_short port_to_connect, + int in, int out) { Channel *c; - int in, out; debug("channel_connect_stdio_fwd %s:%d", host_to_connect, port_to_connect); - in = dup(STDIN_FILENO); - out = dup(STDOUT_FILENO); - if (in < 0 || out < 0) - fatal("channel_connect_stdio_fwd: dup() in/out failed"); - c = channel_new("stdio-forward", SSH_CHANNEL_OPENING, in, out, -1, CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, 0, "stdio-forward", /*nonblock*/0); @@ -1728,34 +1747,6 @@ channel_handle_efd(Channel *c, fd_set *r /* 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)) - 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) { if (c->type == SSH_CHANNEL_OPEN && @@ -1785,10 +1776,130 @@ channel_post_open(Channel *c, fd_set *re 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; + +/* debug3("%s: channel %d: entering, need %u have %u", + __func__, c->self, need, buffer_len(&c->input)); */ + 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); + } +/* debug3("%s: channel %d: done, need %u have %u", + __func__, c->self, need, buffer_len(&c->input)); */ + 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 && 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; + c->mux_rcb(c, c->mux_ctx); + } + + 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)) { + 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, NULL); + /* 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) @@ -1817,6 +1928,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; @@ -1825,6 +1938,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 Index: channels.h =================================================================== RCS file: /cvs/src/usr.bin/ssh/channels.h,v retrieving revision 1.102 diff -u -p -r1.102 channels.h --- channels.h 11 Jan 2010 01:39:46 -0000 1.102 +++ channels.h 18 Jan 2010 04:20:34 -0000 @@ -53,7 +53,9 @@ #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; @@ -81,6 +83,9 @@ struct channel_connect { struct addrinfo *ai, *aitop; }; +/* Callbacks for mux channels back into client-specific code */ +typedef void mux_callback_fn(struct Channel *, void *); + struct Channel { int type; /* channel type/state */ int self; /* my own channel identifier */ @@ -92,7 +97,7 @@ 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 client_tty; /* (client) TTY has been requested */ int force_drain; /* force close on iEOF */ @@ -141,6 +146,10 @@ 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; }; #define CHAN_EXTENDED_IGNORE 0 @@ -171,6 +180,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 @@ -242,7 +252,7 @@ void channel_clear_adm_permitted_opens( 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); +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: clientloop.c =================================================================== RCS file: /cvs/src/usr.bin/ssh/clientloop.c,v retrieving revision 1.216 diff -u -p -r1.216 clientloop.c --- clientloop.c 9 Jan 2010 05:04:24 -0000 1.216 +++ clientloop.c 18 Jan 2010 04:20:35 -0000 @@ -113,7 +113,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 @@ -138,7 +138,7 @@ static volatile sig_atomic_t received_si static int in_non_blocking_mode = 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. */ @@ -556,9 +556,6 @@ client_wait_until_can_do_something(fd_se 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 @@ -686,7 +683,7 @@ client_status_confirm(int type, Channel /* 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", @@ -830,6 +827,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 */ @@ -927,7 +925,7 @@ process_escapes(Channel *c, Buffer *bin, 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; @@ -937,7 +935,7 @@ process_escapes(Channel *c, Buffer *bin, 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 " @@ -982,7 +980,7 @@ process_escapes(Channel *c, Buffer *bin, continue; case '&': - if (c && c->ctl_fd != -1) + if (c && c->ctl_chan != -1) goto noescape; /* * Detach the program (continue to serve @@ -1033,7 +1031,7 @@ process_escapes(Channel *c, Buffer *bin, 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\ @@ -1082,7 +1080,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; @@ -1315,8 +1313,6 @@ client_loop(int have_pty, int escape_cha 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 */ @@ -1434,12 +1430,6 @@ client_loop(int have_pty, int escape_cha /* 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; @@ -1841,9 +1831,8 @@ client_input_channel_req(int type, u_int chan_rcvd_eow(c); } else if (strcmp(rtype, "exit-status") == 0) { exitval = packet_get_int(); - if (c->ctl_fd != -1) { - /* Dispatch to mux client */ - atomicio(vwrite, c->ctl_fd, &exitval, sizeof(exitval)); + if (c->ctl_chan != -1) { + mux_exit_message(c, exitval); success = 1; } else if (id == session_ident) { /* Record exit value of local session */ Index: clientloop.h =================================================================== RCS file: /cvs/src/usr.bin/ssh/clientloop.h,v retrieving revision 1.22 diff -u -p -r1.22 clientloop.h --- clientloop.h 12 Jun 2008 15:19:17 -0000 1.22 +++ clientloop.h 18 Jan 2010 04:20:35 -0000 @@ -56,18 +56,14 @@ typedef void global_confirm_cb(int, u_in 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) */ void muxserver_listen(void); -int muxserver_accept_control(void); void muxclient(const char *); +void mux_exit_message(Channel *, int); Index: mux.c =================================================================== RCS file: /cvs/src/usr.bin/ssh/mux.c,v retrieving revision 1.9 diff -u -p -r1.9 mux.c --- mux.c 9 Jan 2010 05:04:24 -0000 1.9 +++ mux.c 18 Jan 2010 04:20:35 -0000 @@ -17,21 +17,20 @@ /* ssh session multiplexing support */ +// XXX signal of slave passed to master + /* * 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 (after #3 obviously) + * - Maybe extension mechanisms for multi-X11/multi-agent forwarding + * - Document the mux mini-protocol somewhere. + * - 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 @@ -43,6 +42,7 @@ #include #include +#include #include #include #include @@ -53,6 +53,7 @@ #include #include +#include "atomicio.h" #include "xmalloc.h" #include "log.h" #include "ssh.h" @@ -77,13 +78,16 @@ extern int stdin_null_flag; extern char *host; 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; @@ -102,268 +106,238 @@ static volatile sig_atomic_t muxclient_t /* 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; - - 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.sun_len = offsetof(struct sockaddr_un, sun_path) + - strlen(options.control_path) + 1; +struct mux_master_state { + enum { MUX_HELLO_SEND, MUX_HELLO_WAIT, MUX_UP, MUX_SESSION } conn_state; +}; - if (strlcpy(addr.sun_path, options.control_path, - sizeof(addr.sun_path)) >= sizeof(addr.sun_path)) - fatal("ControlPath too long"); +/* 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_FORWARD 0x10000006 +#define MUX_C_CLOSE_FORWARD 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 + +/* type codes for MUX_C_OPEN_FORWARD and MUX_C_CLOSE_FORWARD */ +#define MUX_FWD_LOCAL 1 +#define MUX_FWD_REMOTE 2 +#define MUX_FWD_DYNAMIC 3 + +static void mux_session_confirm(int, void *); + +static int process_mux_master_hello(struct mux_master_state *, Channel *, + Buffer *, Buffer *); +static int process_mux_new_session(struct mux_master_state *, Channel *, + Buffer *, Buffer *); +static int process_mux_alive_check(struct mux_master_state *, Channel *, + Buffer *, Buffer *); +static int process_mux_terminate(struct mux_master_state *, Channel *, + Buffer *, Buffer *); +static int process_mux_open_forward(struct mux_master_state *, Channel *, + Buffer *, Buffer *); +static int process_mux_close_forward(struct mux_master_state *, Channel *, + Buffer *, Buffer *); +static int process_mux_stdio_fwd(struct mux_master_state *, Channel *, + Buffer *, Buffer *); + +static const struct { + u_int type; + int (*handler)(struct mux_master_state *, 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_FORWARD, process_mux_open_forward }, + { MUX_C_CLOSE_FORWARD, process_mux_close_forward }, + { MUX_C_NEW_STDIO_FWD, process_mux_stdio_fwd }, + { 0, NULL } +}; - if ((muxserver_sock = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) - fatal("%s socket(): %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); - old_umask = umask(0177); - if (bind(muxserver_sock, (struct sockaddr *)&addr, addr.sun_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)); + 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 at openssh.com", 0); - packet_send(); - } + Channel *sc, *c = channel_by_id(cid); - 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); + 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) + debug2("%s: channel %d n session channel %d", + __func__, c->self, c->remote_id); + c->remote_id = -1; + sc->ctl_chan = -1; + chan_mark_dead(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; - } - if ((ver = buffer_get_char(&m)) != SSHMUX_VER) { - error("%s: wrong client version %d", __func__, ver); - buffer_free(&m); - close(client_fd); - return 0; - } + return 0; +} - allowed = 1; - mux_command = buffer_get_int(&m); - flags = buffer_get_int(&m); +/* Mux master protocol message handlers */ - buffer_clear(&m); +static int +process_mux_master_hello(struct mux_master_state *state, Channel *c, + Buffer *m, Buffer *r) +{ + u_int ver; - 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 (state->conn_state != MUX_HELLO_WAIT) { + error("%s: MUX_MSG_HELLO received in state MUX_UP", __func__); + return -1; + } + 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); + + /* 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); + + 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->conn_state = MUX_UP; + return 0; +} - /* 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; +static int +process_mux_new_session(struct mux_master_state *state, Channel *c, + Buffer *m, Buffer *r) +{ + Channel *nc; + struct mux_session_confirm_ctx *cctx; + char *cmd, *cp; + u_int i, j, len, env_len, escape_char, window, packetmax; + int new_fd[3]; + + if (state->conn_state != MUX_UP) { + error("%s: incorrect state %u (expected %u)", + __func__, state->conn_state, MUX_UP); + return -1; } - if (!allowed) { - error("Refused control connection"); - close(client_fd); - buffer_free(&m); - return 0; + /* Reply for SSHMUX_COMMAND_OPEN */ + cctx = xcalloc(1, sizeof(*cctx)); + cctx->term = NULL; + cmd = NULL; + if (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 (cctx->term != NULL) + xfree(cctx->term); + error("%s: malformed message", __func__); + return -1; } - 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; + 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; + } } - 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); + 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); - 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); /* 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++) @@ -374,38 +348,44 @@ muxserver_accept_control(void) xfree(cctx->env); xfree(cctx->term); buffer_free(&cctx->cmd); - close(client_fd); xfree(cctx); - return 0; + + /* prepare reply */ + buffer_put_int(r, MUX_S_FAILURE); + buffer_put_cstring(r, + "did not receive file descriptors"); + return -1; } } - debug2("%s: got fds stdin %d, stdout %d, stderr %d", __func__, + debug3("%s: got fds stdin %d, stdout %d, stderr %d", __func__, new_fd[0], new_fd[1], new_fd[2]); + if (options.control_master == SSHCTL_MASTER_ASK || + options.control_master == SSHCTL_MASTER_AUTO_ASK) { + if (!ask_permission("Allow shared connection to %s? ", host)) { + debug2("%s: session refused by user", __func__); + close(new_fd[0]); + close(new_fd[1]); + close(new_fd[2]); + xfree(cctx->term); + if (env_len != 0) { + for (i = 0; i < env_len; i++) + xfree(cctx->env[i]); + xfree(cctx->env); + } + buffer_free(&cctx->cmd); + /* prepare reply */ + buffer_put_int(r, MUX_S_PERMISSION_DENIED); + buffer_put_cstring(r, "Permission denied"); + return 0; + } + } + /* Try to pick up ttymodes from client before it goes raw */ if (cctx->want_tty && tcgetattr(new_fd[0], &cctx->tio) == -1) error("%s: tcgetattr: %s", __func__, strerror(errno)); - /* This roundtrip is just for synchronisation of ttymodes */ - buffer_clear(&m); - if (ssh_msg_send(client_fd, SSHMUX_VER, &m) == -1) { - error("%s: client msg_send failed", __func__); - close(client_fd); - close(new_fd[0]); - close(new_fd[1]); - close(new_fd[2]); - buffer_free(&m); - xfree(cctx->term); - if (env_len != 0) { - for (i = 0; i < env_len; i++) - xfree(cctx->env[i]); - xfree(cctx->env); - } - return 0; - } - buffer_free(&m); - /* enable nonblocking unless tty */ if (!isatty(new_fd[0])) set_nonblock(new_fd[0]); @@ -414,257 +394,1048 @@ muxserver_accept_control(void) if (!isatty(new_fd[2])) set_nonblock(new_fd[2]); - set_nonblock(client_fd); - window = CHAN_SES_WINDOW_DEFAULT; packetmax = CHAN_SES_PACKET_DEFAULT; if (cctx->want_tty) { window >>= 1; packetmax >>= 1; } - - c = channel_new("session", SSH_CHANNEL_OPENING, + + nc = channel_new("session", SSH_CHANNEL_OPENING, new_fd[0], new_fd[1], new_fd[2], window, packetmax, CHAN_EXTENDED_WRITE, "client-session", /*nonblock*/0); - c->ctl_fd = client_fd; + nc->ctl_chan = c->self; /* link session -> control channel */ + c->remote_id = nc->self; /* link control -> session channel */ + if (cctx->want_tty && escape_char != 0xffffffff) { - channel_register_filter(c->self, + channel_register_filter(nc->self, client_simple_escape_filter, NULL, client_filter_cleanup, client_new_escape_filter_ctx((int)escape_char)); } - debug3("%s: channel_new: %d", __func__, c->self); - - channel_send_open(c->self); - channel_register_open_confirm(c->self, mux_session_confirm, cctx); - return 0; -} + debug2("%s: channel_new: %d linked to control channel %d", + __func__, nc->self, nc->ctl_chan); -/* ** Multiplexing client support */ + channel_send_open(nc->self); + channel_register_open_confirm(nc->self, mux_session_confirm, cctx); + channel_register_cleanup(nc->self, mux_master_session_cleanup_cb, 0); + + /* prepare reply */ + /* XXX defer until mux_session_confirm() fires */ + buffer_put_int(r, MUX_S_OK); + state->conn_state = MUX_SESSION; -/* Exit signal handler */ -static void -control_client_sighandler(int signo) -{ - muxclient_terminate = signo; + return 0; } -/* - * Relay signal handler - used to pass some signals from mux client to - * mux master. - */ -static void -control_client_sigrelay(int signo) +static int +process_mux_alive_check(struct mux_master_state *state, Channel *c, + Buffer *m, Buffer *r) { - int save_errno = errno; + debug2("%s: channel %d: alive check", __func__, c->self); - if (muxserver_pid > 1) - kill(muxserver_pid, signo); + /* prepare reply */ + buffer_put_int(r, MUX_S_ALIVE); + buffer_put_int(r, (u_int)getpid()); - errno = save_errno; + return 0; } -/* Check mux client environment variables before passing them to mux master. */ static int -env_permitted(char *env) +process_mux_terminate(struct mux_master_state *state, Channel *c, + Buffer *m, Buffer *r) { - int i, ret; - char name[1024], *cp; - - if ((cp = strchr(env, '=')) == NULL || cp == env) - return (0); - ret = snprintf(name, sizeof(name), "%.*s", (int)(cp - env), env); - if (ret <= 0 || (size_t)ret >= sizeof(name)) - fatal("env_permitted: name '%.100s...' too long", env); + debug2("%s: channel %d: terminate request", __func__, c->self); - for (i = 0; i < options.num_send_env; i++) - if (match_pattern(name, options.send_env[i])) - return (1); + if (options.control_master == SSHCTL_MASTER_ASK || + options.control_master == SSHCTL_MASTER_AUTO_ASK) { + if (!ask_permission("Terminate shared connection to %s? ", + host)) { + debug2("%s: termination refused by user", __func__); + buffer_put_int(r, MUX_S_PERMISSION_DENIED); + buffer_put_cstring(r, "Permission denied"); + return 0; + } + } - return (0); + quit_pending = 1; + buffer_put_int(r, MUX_S_OK); + /* XXX exit happens too soon - message never makes it to client */ + return 0; } -/* Multiplex client main loop. */ -void -muxclient(const char *path) +static char * +format_forward(u_int ftype, Forward *fwd) { - struct sockaddr_un addr; - int i, r, fd, sock, exitval[2], num_env; - Buffer m; - char *term; - extern char **environ; - u_int allowed, flags; - - if (muxclient_command == 0) - muxclient_command = SSHMUX_COMMAND_OPEN; + char *ret; - switch (options.control_master) { - case SSHCTL_MASTER_AUTO: - case SSHCTL_MASTER_AUTO_ASK: - debug("auto-mux: Trying existing master"); - /* FALLTHROUGH */ - case SSHCTL_MASTER_NO: + switch (ftype) { + case MUX_FWD_LOCAL: + xasprintf(&ret, "local forward %.200s:%d -> %.200s:%d", + (fwd->listen_host == NULL) ? + (options.gateway_ports ? "*" : "LOCALHOST") : + fwd->listen_host, fwd->listen_port, + fwd->connect_host, fwd->connect_port); + break; + case MUX_FWD_DYNAMIC: + xasprintf(&ret, "dynamic forward %.200s:%d -> *", + (fwd->listen_host == NULL) ? + (options.gateway_ports ? "*" : "LOCALHOST") : + fwd->listen_host, fwd->listen_port); + break; + case MUX_FWD_REMOTE: + xasprintf(&ret, "remote forward %.200s:%d -> %.200s:%d", + (fwd->listen_host == NULL) ? + "LOCALHOST" : fwd->listen_host, + fwd->listen_port, + fwd->connect_host, fwd->connect_port); break; default: - return; + fatal("%s: unknown forward type %u", __func__, ftype); } + return ret; +} - memset(&addr, '\0', sizeof(addr)); - addr.sun_family = AF_UNIX; - addr.sun_len = offsetof(struct sockaddr_un, sun_path) + - strlen(path) + 1; +static int +compare_host(const char *a, const char *b) +{ + if (a == NULL && b == NULL) + return 1; + if (a == NULL || b == NULL) + return 0; + return strcmp(a, b) == 0; +} - if (strlcpy(addr.sun_path, path, - sizeof(addr.sun_path)) >= sizeof(addr.sun_path)) - fatal("ControlPath too long"); +static int +compare_forward(Forward *a, Forward *b) +{ + if (!compare_host(a->listen_host, b->listen_host)) + return 0; + if (a->listen_port != b->listen_port) + return 0; + if (!compare_host(a->connect_host, b->connect_host)) + return 0; + if (a->connect_port != b->connect_port) + return 0; - if ((sock = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) - fatal("%s socket(): %s", __func__, strerror(errno)); + return 1; +} - if (connect(sock, (struct sockaddr *)&addr, addr.sun_len) == -1) { - if (muxclient_command != SSHMUX_COMMAND_OPEN) { - fatal("Control socket connect(%.100s): %s", path, - strerror(errno)); - } - if (errno == ENOENT) - debug("Control socket \"%.100s\" does not exist", path); - else { - error("Control socket connect(%.100s): %s", path, - strerror(errno)); +static int +process_mux_open_forward(struct mux_master_state *state, Channel *c, + Buffer *m, Buffer *r) +{ + Forward fwd; + char *fwd_desc = NULL; + u_int ftype; + int i, ret = 0, freefwd = 1; + + fwd.listen_host = fwd.connect_host = NULL; + if (buffer_get_int_ret(&ftype, m) != 0 || + (fwd.listen_host = buffer_get_string_ret(m, NULL)) == NULL || + buffer_get_int_ret(&fwd.listen_port, m) != 0 || + (fwd.connect_host = buffer_get_string_ret(m, NULL)) == NULL || + buffer_get_int_ret(&fwd.connect_port, m) != 0) { + error("%s: malformed message", __func__); + ret = -1; + goto out; + } + + if (*fwd.listen_host == '\0') { + xfree(fwd.listen_host); + fwd.listen_host = NULL; + } + if (*fwd.connect_host == '\0') { + xfree(fwd.connect_host); + fwd.connect_host = NULL; + } + + debug2("%s: channel %d: request %s", __func__, c->self, + (fwd_desc = format_forward(ftype, &fwd))); + + if (ftype != MUX_FWD_LOCAL && ftype != MUX_FWD_REMOTE && + ftype != MUX_FWD_DYNAMIC) { + logit("%s: invalid forwarding type %u", __func__, ftype); + invalid: + xfree(fwd.listen_host); + xfree(fwd.connect_host); + buffer_put_int(r, MUX_S_FAILURE); + buffer_put_cstring(r, "Invalid forwarding request"); + return 0; + } + /* XXX support rport0 forwarding with reply of port assigned */ + if (fwd.listen_port == 0 || fwd.listen_port >= 65536) { + logit("%s: invalid listen port %u", __func__, + fwd.listen_port); + goto invalid; + } + if (fwd.connect_port >= 65536 || (ftype != MUX_FWD_DYNAMIC && + ftype != MUX_FWD_REMOTE && fwd.connect_port == 0)) { + logit("%s: invalid connect port %u", __func__, + fwd.connect_port); + goto invalid; + } + if (ftype != MUX_FWD_DYNAMIC && fwd.connect_host == NULL) { + logit("%s: missing connect host", __func__); + goto invalid; + } + + /* Skip forwards that have already been requested */ + switch (ftype) { + case MUX_FWD_LOCAL: + case MUX_FWD_DYNAMIC: + for (i = 0; i < options.num_local_forwards; i++) { + if (compare_forward(&fwd, + options.local_forwards + i)) { + exists: + debug2("%s: found existing forwarding", + __func__); + buffer_put_int(r, MUX_S_OK); + goto out; + } } - close(sock); + break; + case MUX_FWD_REMOTE: + for (i = 0; i < options.num_remote_forwards; i++) { + if (compare_forward(&fwd, + options.remote_forwards + i)) + goto exists; + } + break; + } + + if (options.control_master == SSHCTL_MASTER_ASK || + options.control_master == SSHCTL_MASTER_AUTO_ASK) { + if (!ask_permission("Open %s on %s?", fwd_desc, host)) { + debug2("%s: forwarding refused by user", __func__); + buffer_put_int(r, MUX_S_PERMISSION_DENIED); + buffer_put_cstring(r, "Permission denied"); + goto out; + } + } + + if (ftype == MUX_FWD_LOCAL || ftype == MUX_FWD_DYNAMIC) { + if (options.num_local_forwards + 1 >= + SSH_MAX_FORWARDS_PER_DIRECTION || + channel_setup_local_fwd_listener(fwd.listen_host, + fwd.listen_port, fwd.connect_host, fwd.connect_port, + options.gateway_ports) < 0) { + fail: + logit("slave-requested %s failed", fwd_desc); + buffer_put_int(r, MUX_S_FAILURE); + buffer_put_cstring(r, "Port forwarding failed"); + goto out; + } + add_local_forward(&options, &fwd); + freefwd = 0; + } else { + /* XXX wait for remote to confirm */ + if (options.num_remote_forwards + 1 >= + SSH_MAX_FORWARDS_PER_DIRECTION || + channel_request_remote_forwarding(fwd.listen_host, + fwd.listen_port, fwd.connect_host, fwd.connect_port) < 0) + goto fail; + add_remote_forward(&options, &fwd); + freefwd = 0; + } + buffer_put_int(r, MUX_S_OK); + out: + if (fwd_desc != NULL) + xfree(fwd_desc); + if (freefwd) { + if (fwd.listen_host != NULL) + xfree(fwd.listen_host); + if (fwd.connect_host != NULL) + xfree(fwd.connect_host); + } + return ret; +} + +static int +process_mux_close_forward(struct mux_master_state *state, Channel *c, + Buffer *m, Buffer *r) +{ + Forward fwd; + char *fwd_desc = NULL; + u_int ftype; + int ret = 0; + + fwd.listen_host = fwd.connect_host = NULL; + if (buffer_get_int_ret(&ftype, m) != 0 || + (fwd.listen_host = buffer_get_string_ret(m, NULL)) == NULL || + buffer_get_int_ret(&fwd.listen_port, m) != 0 || + (fwd.connect_host = buffer_get_string_ret(m, NULL)) == NULL || + buffer_get_int_ret(&fwd.connect_port, m) != 0) { + error("%s: malformed message", __func__); + ret = -1; + goto out; + } + + if (*fwd.listen_host == '\0') { + xfree(fwd.listen_host); + fwd.listen_host = NULL; + } + if (*fwd.connect_host == '\0') { + xfree(fwd.connect_host); + fwd.connect_host = NULL; + } + + debug2("%s: channel %d: request %s", __func__, c->self, + (fwd_desc = format_forward(ftype, &fwd))); + + /* XXX implement this */ + buffer_put_int(r, MUX_S_FAILURE); + buffer_put_cstring(r, "unimplemented"); + + out: + if (fwd_desc != NULL) + xfree(fwd_desc); + if (fwd.listen_host != NULL) + xfree(fwd.listen_host); + if (fwd.connect_host != NULL) + xfree(fwd.connect_host); + + return ret; +} + +static int +process_mux_stdio_fwd(struct mux_master_state *state, Channel *c, + Buffer *m, Buffer *r) +{ + Channel *nc; + char *chost; + u_int cport, i, j; + int new_fd[2]; + + if (state->conn_state != MUX_UP) { + error("%s: incorrect state %u (expected %u)", + __func__, state->conn_state, MUX_UP); + return -1; + } + + if ((chost = buffer_get_string_ret(m, NULL)) == NULL || + buffer_get_int_ret(&cport, m) != 0) { + if (chost != NULL) + xfree(chost); + error("%s: malformed message", __func__); + return -1; + } + + debug2("%s: channel %d: request stdio fwd to %s:%u", + __func__, c->self, chost, cport); + + /* Gather fds from client */ + for(i = 0; i < 2; i++) { + 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++) + close(new_fd[j]); + xfree(chost); + + /* prepare reply */ + buffer_put_int(r, MUX_S_FAILURE); + buffer_put_cstring(r, + "did not receive file descriptors"); + return -1; + } + } + + debug3("%s: got fds stdin %d, stdout %d", __func__, + new_fd[0], new_fd[1]); + + if (options.control_master == SSHCTL_MASTER_ASK || + options.control_master == SSHCTL_MASTER_AUTO_ASK) { + if (!ask_permission("Allow forward to to %s:%u? ", + chost, cport)) { + debug2("%s: stdio fwd refused by user", __func__); + close(new_fd[0]); + close(new_fd[1]); + xfree(chost); + /* prepare reply */ + buffer_put_int(r, MUX_S_PERMISSION_DENIED); + buffer_put_cstring(r, "Permission denied"); + return 0; + } + } + + /* enable nonblocking unless tty */ + if (!isatty(new_fd[0])) + set_nonblock(new_fd[0]); + if (!isatty(new_fd[1])) + set_nonblock(new_fd[1]); + + nc = channel_connect_stdio_fwd(chost, cport, new_fd[0], new_fd[1]); + + nc->ctl_chan = c->self; /* link session -> control channel */ + c->remote_id = nc->self; /* link control -> session channel */ + + debug2("%s: channel_new: %d linked to control channel %d", + __func__, nc->self, nc->ctl_chan); + + channel_register_cleanup(nc->self, mux_master_session_cleanup_cb, 0); + + /* prepare reply */ + /* XXX defer until channel confirmed */ + buffer_put_int(r, MUX_S_OK); + state->conn_state = MUX_SESSION; + + return 0; +} + +/* Channel callbacks fired on read/write from mux slave fd */ +static void +mux_master_read_cb(Channel *c, void *ctx) +{ + struct mux_master_state *state = (struct mux_master_state *)ctx; + Buffer in, out; + void *ptr; + u_int type, have, i; + int ret = -1; + + /* Complete setup of channel */ + if (ctx == NULL) { + state = xcalloc(1, sizeof(state)); + state->conn_state = MUX_HELLO_SEND; + c->mux_ctx = ctx = state; + channel_register_cleanup(c->self, + mux_master_control_cleanup_cb, 0); + } + +/* debug3("%s: enter channel %d ibuf len %u obuf len %u state %d", + __func__, c->self, buffer_len(&c->input), buffer_len(&c->output), + state->conn_state); */ + + switch (state->conn_state) { + case MUX_HELLO_SEND: + buffer_init(&out); + buffer_put_int(&out, MUX_MSG_HELLO); + buffer_put_int(&out, SSHMUX_VER); + /* no extensions */ + buffer_put_string(&c->output, buffer_ptr(&out), + buffer_len(&out)); + buffer_free(&out); + state->conn_state = MUX_HELLO_WAIT; + debug3("%s: channel %d: hello sent", __func__, c->self); + ret = 0; + break; + case MUX_HELLO_WAIT: + case MUX_UP: + case MUX_SESSION: + buffer_init(&in); + buffer_init(&out); + + /* Channel code ensures that we receive whole packets */ + if ((ptr = buffer_get_string_ptr_ret(&c->input, + &have)) == NULL) { + malf: + error("%s: malformed message", __func__); + goto out; + } + buffer_append(&in, ptr, have); + + if (buffer_get_int_ret(&type, &in)) + goto malf; + debug3("%s: channel %d packet type 0x%08x len %u", + __func__, c->self, type, buffer_len(&in)); + + if (state->conn_state == MUX_HELLO_WAIT && + type != MUX_MSG_HELLO) { + error("%s: expected MUX_MSG_HELLO(0x%08x), " + "received 0x%08x", __func__, MUX_MSG_HELLO, type); + goto out; + } + + for (i = 0; mux_master_handlers[i].handler != NULL; i++) { + if (type == mux_master_handlers[i].type) { + ret = mux_master_handlers[i].handler(state, + c, &in, &out); + break; + } + } + if (mux_master_handlers[i].handler == NULL) { + error("%s: unsupported mux message 0x%08x", + __func__, type); + buffer_put_int(&out, MUX_S_FAILURE); + buffer_put_cstring(&out, "unsupported request"); + ret = 0; + } + /* Enqueue reply packet */ + if (buffer_len(&out) != 0) { + buffer_put_string(&c->output, buffer_ptr(&out), + buffer_len(&out)); + } + out: +/* debug3("%s: reply channel %d ibuf len %u obuf len %u state %d", + __func__, c->self, buffer_len(&c->input), + buffer_len(&c->output), state->conn_state); */ + + buffer_free(&in); + buffer_free(&out); + break; + default: + fatal("%s: unknown state %d", __func__, state->conn_state); + } +} + +void +mux_exit_message(Channel *c, int exitval) +{ + Buffer m; + Channel *mux_chan; + + debug3("%s: channel %d: exit message, evitval %d", __func__, c->self, + exitval); + + if ((mux_chan = channel_by_id(c->ctl_chan)) == NULL) + fatal("%s: channel %d missing mux channel %d", + __func__, c->self, c->ctl_chan); + + /* Append exit message packet to control socket output queue */ + buffer_init(&m); + buffer_put_int(&m, MUX_S_EXIT_MESSAGE); + buffer_put_int(&m, exitval); + + buffer_put_string(&mux_chan->output, buffer_ptr(&m), buffer_len(&m)); + buffer_free(&m); +} + +/* Prepare a mux master to listen on a Unix domain socket. */ +void +muxserver_listen(void) +{ + struct sockaddr_un addr; + mode_t old_umask; + + 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.sun_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"); + + if ((muxserver_sock = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) + fatal("%s socket(): %s", __func__, strerror(errno)); + + old_umask = umask(0177); + if (bind(muxserver_sock, (struct sockaddr *)&addr, addr.sun_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)); } + umask(old_umask); - if (stdin_null_flag) { - if ((fd = open(_PATH_DEVNULL, O_RDONLY)) == -1) - fatal("open(/dev/null): %s", strerror(errno)); - if (dup2(fd, STDIN_FILENO) == -1) - fatal("dup2: %s", strerror(errno)); - if (fd > STDERR_FILENO) - close(fd); + if (listen(muxserver_sock, 64) == -1) + fatal("%s listen(): %s", __func__, strerror(errno)); + + set_nonblock(muxserver_sock); + + mux_listener_channel = channel_new("mux listener", + SSH_CHANNEL_MUX_LISTENER, muxserver_sock, muxserver_sock, -1, + CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, + 0, addr.sun_path, 1); + mux_listener_channel->mux_rcb = mux_master_read_cb; + debug3("%s: mux listener channel %d fd %d", __func__, + mux_listener_channel->self, mux_listener_channel->sock); +} + +/* Callback on open confirmation in mux master for a mux client session. */ +static void +mux_session_confirm(int id, void *arg) +{ + 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_by_id(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 */ } - term = getenv("TERM"); + if (cctx->want_agent_fwd && options.forward_agent) { + debug("Requesting authentication agent forwarding."); + channel_request_start(id, "auth-agent-req at openssh.com", 0); + packet_send(); + } - flags = 0; - if (tty_flag) - flags |= SSHMUX_FLAG_TTY; - if (subsystem_flag) - flags |= SSHMUX_FLAG_SUBSYS; - if (options.forward_x11) - flags |= SSHMUX_FLAG_X11_FWD; - if (options.forward_agent) - flags |= SSHMUX_FLAG_AGENT_FWD; + client_session2_setup(id, cctx->want_tty, cctx->want_subsys, + cctx->term, &cctx->tio, c->rfd, &cctx->cmd, cctx->env); - signal(SIGPIPE, SIG_IGN); + 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); + } + xfree(cctx); +} + +/* ** Multiplexing client support */ + +/* Exit signal handler */ +static void +control_client_sighandler(int signo) +{ + muxclient_terminate = signo; +} + +/* + * Relay signal handler - used to pass some signals from mux client to + * mux master. + */ +static void +control_client_sigrelay(int signo) +{ + int save_errno = errno; + + if (muxserver_pid > 1) + kill(muxserver_pid, signo); + + errno = save_errno; +} + +static int +mux_client_read(int fd, Buffer *b, u_int need) +{ + u_int have; + ssize_t len; + u_char *p; + struct pollfd pfd; + + pfd.fd = fd; + pfd.events = POLLIN; + p = buffer_append_space(b, need); + for (have = 0; have < need; ) { + if (muxclient_terminate) { + errno = EINTR; + return -1; + } + len = read(fd, p + have, need - have); + if (len < 0) { + switch (errno) { + case EAGAIN: + (void)poll(&pfd, 1, -1); + /* FALLTHROUGH */ + case EINTR: + continue; + default: + return -1; + } + } + if (len == 0) { + errno = EPIPE; + return -1; + } + have += (u_int)len; + } + return 0; +} + +static int +mux_client_write_packet(int fd, Buffer *m) +{ + Buffer queue; + u_int have, need; + int oerrno, len; + u_char *ptr; + struct pollfd pfd; + + pfd.fd = fd; + pfd.events = POLLOUT; + buffer_init(&queue); + buffer_put_string(&queue, buffer_ptr(m), buffer_len(m)); + + need = buffer_len(&queue); + ptr = buffer_ptr(&queue); + + for (have = 0; have < need; ) { + if (muxclient_terminate) { + buffer_free(&queue); + errno = EINTR; + return -1; + } + len = write(fd, ptr + have, need - have); + if (len < 0) { + switch (errno) { + case EAGAIN: + (void)poll(&pfd, 1, -1); + /* FALLTHROUGH */ + case EINTR: + continue; + default: + oerrno = errno; + buffer_free(&queue); + errno = oerrno; + return -1; + } + } + if (len == 0) { + buffer_free(&queue); + errno = EPIPE; + return -1; + } + have += (u_int)len; + } + buffer_free(&queue); + return 0; +} + +static int +mux_client_read_packet(int fd, Buffer *m) +{ + Buffer queue; + u_int need, have; + void *ptr; + int oerrno; + + buffer_init(&queue); + if (mux_client_read(fd, &queue, 4) != 0) { + if ((oerrno = errno) == EPIPE) + debug3("%s: read header failed: %s", __func__, strerror(errno)); + errno = oerrno; + return -1; + } + need = get_u32(buffer_ptr(&queue)); + if (mux_client_read(fd, &queue, need) != 0) { + oerrno = errno; + debug3("%s: read body failed: %s", __func__, strerror(errno)); + errno = oerrno; + return -1; + } + ptr = buffer_get_string_ptr(&queue, &have); + buffer_append(m, ptr, have); + buffer_free(&queue); + return 0; +} + +static int +mux_client_hello_exchange(int fd) +{ + Buffer m; + u_int type, ver; buffer_init(&m); + buffer_put_int(&m, MUX_MSG_HELLO); + buffer_put_int(&m, SSHMUX_VER); + /* no extensions */ - /* Send our command to server */ - buffer_put_int(&m, muxclient_command); - buffer_put_int(&m, flags); - if (ssh_msg_send(sock, SSHMUX_VER, &m) == -1) { - error("%s: msg_send", __func__); - muxerr: - close(sock); + if (mux_client_write_packet(fd, &m) != 0) + fatal("%s: write packet: %s", __func__, strerror(errno)); + + buffer_clear(&m); + + /* Read their HELLO */ + if (mux_client_read_packet(fd, &m) != 0) { buffer_free(&m); - if (muxclient_command != SSHMUX_COMMAND_OPEN) - cleanup_exit(255); - logit("Falling back to non-multiplexed connection"); - xfree(options.control_path); - options.control_path = NULL; - options.control_master = SSHCTL_MASTER_NO; - return; + return -1; } + + type = buffer_get_int(&m); + if (type != MUX_MSG_HELLO) + fatal("%s: expected HELLO (%u) received %u", + __func__, MUX_MSG_HELLO, type); + ver = buffer_get_int(&m); + if (ver != SSHMUX_VER) + fatal("Unsupported multiplexing protocol version %d " + "(expected %d)", ver, SSHMUX_VER); + debug2("%s: master version %u", __func__, ver); + /* No extensions are presently defined */ + while (buffer_len(&m) > 0) { + char *name = buffer_get_string(&m, NULL); + char *value = buffer_get_string(&m, NULL); + + debug2("Unrecognised master extension \"%s\"", name); + xfree(name); + xfree(value); + } + buffer_free(&m); + return 0; +} + +static u_int +mux_client_request_alive(int fd) +{ + Buffer m; + char *e; + u_int pid, type; + + debug3("%s: entering", __func__); + + buffer_init(&m); + buffer_put_int(&m, MUX_C_ALIVE_CHECK); + + if (mux_client_write_packet(fd, &m) != 0) + fatal("%s: write packet: %s", __func__, strerror(errno)); + buffer_clear(&m); - /* Get authorisation status and PID of controlee */ - if (ssh_msg_recv(sock, &m) == -1) { - error("%s: Did not receive reply from master", __func__); - goto muxerr; - } - if (buffer_get_char(&m) != SSHMUX_VER) { - error("%s: Master replied with wrong version", __func__); - goto muxerr; - } - if (buffer_get_int_ret(&allowed, &m) != 0) { - error("%s: bad server reply", __func__); - goto muxerr; - } - if (allowed != 1) { - error("Connection to master denied"); - goto muxerr; + /* Read their reply */ + if (mux_client_read_packet(fd, &m) != 0) { + buffer_free(&m); + return 0; + } + + type = buffer_get_int(&m); + if (type != MUX_S_ALIVE) { + e = buffer_get_string(&m, NULL); + fatal("%s: master returned error: %s", __func__, e); } - muxserver_pid = buffer_get_int(&m); + + pid = buffer_get_int(&m); + buffer_free(&m); + + debug3("%s: done pid = %u", __func__, pid); + + return pid; +} + +static void +mux_client_request_terminate(int fd) +{ + Buffer m; + char *e; + u_int type; + + debug3("%s: entering", __func__); + + buffer_init(&m); + buffer_put_int(&m, MUX_C_TERMINATE); + + if (mux_client_write_packet(fd, &m) != 0) + fatal("%s: write packet: %s", __func__, strerror(errno)); buffer_clear(&m); - switch (muxclient_command) { - case SSHMUX_COMMAND_ALIVE_CHECK: - fprintf(stderr, "Master running (pid=%d)\r\n", - muxserver_pid); - exit(0); - case SSHMUX_COMMAND_TERMINATE: - fprintf(stderr, "Exit request sent.\r\n"); - exit(0); - case SSHMUX_COMMAND_OPEN: - buffer_put_cstring(&m, term ? term : ""); - if (options.escape_char == SSH_ESCAPECHAR_NONE) - buffer_put_int(&m, 0xffffffff); - else - buffer_put_int(&m, options.escape_char); - buffer_append(&command, "\0", 1); - buffer_put_cstring(&m, buffer_ptr(&command)); - - if (options.num_send_env == 0 || environ == NULL) { - buffer_put_int(&m, 0); - } else { - /* Pass environment */ - num_env = 0; - for (i = 0; environ[i] != NULL; i++) { - if (env_permitted(environ[i])) - num_env++; /* Count */ - } - buffer_put_int(&m, num_env); - for (i = 0; environ[i] != NULL && num_env >= 0; i++) { - if (env_permitted(environ[i])) { - num_env--; - buffer_put_cstring(&m, environ[i]); - } - } + /* Read their reply */ + if (mux_client_read_packet(fd, &m) != 0) { + /* Remote end exited already */ + if (errno == EPIPE) { + buffer_free(&m); + return; } + fatal("%s: read from master failed: %s", + __func__, strerror(errno)); + } + + type = buffer_get_int(&m); + switch (type) { + case MUX_S_OK: break; + case MUX_S_PERMISSION_DENIED: + e = buffer_get_string(&m, NULL); + fatal("Master refused termination request: %s", e); + case MUX_S_FAILURE: + e = buffer_get_string(&m, NULL); + fatal("%s: termination request failed: %s", __func__, e); default: - fatal("unrecognised muxclient_command %d", muxclient_command); + fatal("%s: unexpected response from master 0x%08x", + __func__, type); + } + buffer_free(&m); +} + +static int +mux_client_request_forward(int fd, u_int ftype, Forward *fwd) +{ + Buffer m; + char *e, *fwd_desc; + u_int type; + + fwd_desc = format_forward(ftype, fwd); + debug("Requesting %s", fwd_desc); + xfree(fwd_desc); + + buffer_init(&m); + buffer_put_int(&m, MUX_C_OPEN_FORWARD); + buffer_put_int(&m, ftype); + buffer_put_cstring(&m, + fwd->listen_host == NULL ? "" : fwd->listen_host); + buffer_put_int(&m, fwd->listen_port); + buffer_put_cstring(&m, + fwd->connect_host == NULL ? "" : fwd->connect_host); + buffer_put_int(&m, fwd->connect_port); + + if (mux_client_write_packet(fd, &m) != 0) + fatal("%s: write packet: %s", __func__, strerror(errno)); + + buffer_clear(&m); + + /* Read their reply */ + if (mux_client_read_packet(fd, &m) != 0) { + buffer_free(&m); + return -1; } - if (ssh_msg_send(sock, SSHMUX_VER, &m) == -1) { - error("%s: msg_send", __func__); - goto muxerr; + type = buffer_get_int(&m); + switch (type) { + case MUX_S_OK: + break; + case MUX_S_PERMISSION_DENIED: + e = buffer_get_string(&m, NULL); + buffer_free(&m); + error("Master refused forwarding request: %s", e); + return -1; + case MUX_S_FAILURE: + e = buffer_get_string(&m, NULL); + buffer_free(&m); + error("%s: session request failed: %s", __func__, e); + return -1; + default: + fatal("%s: unexpected response from master 0x%08x", + __func__, type); } + buffer_free(&m); - if (mm_send_fd(sock, STDIN_FILENO) == -1 || - mm_send_fd(sock, STDOUT_FILENO) == -1 || - mm_send_fd(sock, STDERR_FILENO) == -1) { - error("%s: send fds failed", __func__); - goto muxerr; + return 0; +} + +static int +mux_client_request_forwards(int fd) +{ + int i; + + debug3("%s: requesting forwardings: %d local, %d remote", __func__, + options.num_local_forwards, options.num_remote_forwards); + + /* XXX ExitOnForwardingFailure */ + for (i = 0; i < options.num_local_forwards; i++) { + if (mux_client_request_forward(fd, + options.local_forwards[i].connect_port == 0 ? + MUX_FWD_DYNAMIC : MUX_FWD_LOCAL, + options.local_forwards + i) != 0) + return -1; + } + for (i = 0; i < options.num_remote_forwards; i++) { + if (mux_client_request_forward(fd, MUX_FWD_REMOTE, + options.remote_forwards + i) != 0) + return -1; } + return 0; +} - /* - * Mux errors are non-recoverable from this point as the master - * has ownership of the session now. - */ +static int +mux_client_request_session(int fd) +{ + Buffer m; + char *e, *term; + u_int i, exitval, type, exitval_seen; + extern char **environ; + int devnull; + + debug3("%s: entering", __func__); + + if ((muxserver_pid = mux_client_request_alive(fd)) == 0) { + error("%s: master alive request failed", __func__); + return -1; + } - /* Wait for reply, so master has a chance to gather ttymodes */ + signal(SIGPIPE, SIG_IGN); + + if (stdin_null_flag) { + if ((devnull = open(_PATH_DEVNULL, O_RDONLY)) == -1) + fatal("open(/dev/null): %s", strerror(errno)); + if (dup2(devnull, STDIN_FILENO) == -1) + fatal("dup2: %s", strerror(errno)); + if (devnull > STDERR_FILENO) + close(devnull); + } + + term = getenv("TERM"); + + buffer_init(&m); + buffer_put_int(&m, MUX_C_NEW_SESSION); + buffer_put_int(&m, tty_flag); + buffer_put_int(&m, options.forward_x11); + buffer_put_int(&m, options.forward_agent); + buffer_put_int(&m, subsystem_flag); + buffer_put_int(&m, options.escape_char == SSH_ESCAPECHAR_NONE ? + 0xffffffff : (u_int)options.escape_char); + buffer_put_cstring(&m, term == NULL ? "" : term); + buffer_put_string(&m, buffer_ptr(&command), buffer_len(&command)); + + if (options.num_send_env > 0 && environ != NULL) { + /* Pass environment */ + for (i = 0; environ[i] != NULL; i++) { + if (env_permitted(environ[i])) { + buffer_put_cstring(&m, environ[i]); + } + } + } + + if (mux_client_write_packet(fd, &m) != 0) + fatal("%s: write packet: %s", __func__, strerror(errno)); + + /* Send the stdio file descriptors */ + if (mm_send_fd(fd, STDIN_FILENO) == -1 || + mm_send_fd(fd, STDOUT_FILENO) == -1 || + mm_send_fd(fd, STDERR_FILENO) == -1) + fatal("%s: send fds failed", __func__); + + debug3("%s: session request sent", __func__); + + /* Read their reply */ buffer_clear(&m); - if (ssh_msg_recv(sock, &m) == -1) - fatal("%s: msg_recv", __func__); - if (buffer_get_char(&m) != SSHMUX_VER) - fatal("%s: wrong version", __func__); - buffer_free(&m); + if (mux_client_read_packet(fd, &m) != 0) { + error("%s: read from master failed: %s", + __func__, strerror(errno)); + buffer_free(&m); + return -1; + } + + type = buffer_get_int(&m); + switch (type) { + case MUX_S_OK: + break; + case MUX_S_PERMISSION_DENIED: + e = buffer_get_string(&m, NULL); + buffer_free(&m); + error("Master refused forwarding request: %s", e); + return -1; + case MUX_S_FAILURE: + e = buffer_get_string(&m, NULL); + buffer_free(&m); + error("%s: forwarding request failed: %s", __func__, e); + return -1; + default: + buffer_free(&m); + error("%s: unexpected response from master 0x%08x", + __func__, type); + return -1; + } signal(SIGHUP, control_client_sighandler); signal(SIGINT, control_client_sighandler); @@ -676,42 +1447,224 @@ muxclient(const char *path) /* * Stick around until the controlee closes the client_fd. - * Before it does, it is expected to write this process' exit - * value (one int). This process must read the value and wait for - * the closure of the client_fd; if this one closes early, the - * multiplex master will terminate early too (possibly losing data). + * Before it does, it is expected to write an exit message. + * This process must read the value and wait for the closure of + * the client_fd; if this one closes early, the multiplex master will + * terminate early too (possibly losing data). */ - exitval[0] = 0; - for (i = 0; !muxclient_terminate && i < (int)sizeof(exitval);) { - r = read(sock, (char *)exitval + i, sizeof(exitval) - i); - if (r == 0) { - debug2("Received EOF from master"); + for (exitval = 255, exitval_seen = 0;;) { + buffer_clear(&m); + if (mux_client_read_packet(fd, &m) != 0) break; + type = buffer_get_int(&m); + if (type != MUX_S_EXIT_MESSAGE) { + e = buffer_get_string(&m, NULL); + fatal("%s: master returned error: %s", __func__, e); } - if (r == -1) { - if (errno == EINTR) - continue; - fatal("%s: read %s", __func__, strerror(errno)); - } - i += r; + if (exitval_seen) + fatal("%s: exitval sent twice", __func__); + exitval = buffer_get_int(&m); + exitval_seen = 1; } - close(sock); + close(fd); leave_raw_mode(force_tty_flag); - if (i > (int)sizeof(int)) - fatal("%s: master returned too much data (%d > %lu)", - __func__, i, (u_long)sizeof(int)); + if (muxclient_terminate) { debug2("Exiting on signal %d", muxclient_terminate); - exitval[0] = 255; - } else if (i < (int)sizeof(int)) { + exitval = 255; + } else if (!exitval_seen) { debug2("Control master terminated unexpectedly"); - exitval[0] = 255; + exitval = 255; } else - debug2("Received exit status from master %d", exitval[0]); + debug2("Received exit status from master %d", exitval); if (tty_flag && options.log_level != SYSLOG_LEVEL_QUIET) fprintf(stderr, "Shared connection to %s closed.\r\n", host); - exit(exitval[0]); + exit(exitval); +} + +static int +mux_client_request_stdio_fwd(int fd) +{ + Buffer m; + char *e; + u_int type; + int devnull; + + debug3("%s: entering", __func__); + + if ((muxserver_pid = mux_client_request_alive(fd)) == 0) { + error("%s: master alive request failed", __func__); + return -1; + } + + signal(SIGPIPE, SIG_IGN); + + if (stdin_null_flag) { + if ((devnull = open(_PATH_DEVNULL, O_RDONLY)) == -1) + fatal("open(/dev/null): %s", strerror(errno)); + if (dup2(devnull, STDIN_FILENO) == -1) + fatal("dup2: %s", strerror(errno)); + if (devnull > STDERR_FILENO) + close(devnull); + } + + buffer_init(&m); + buffer_put_int(&m, MUX_C_NEW_STDIO_FWD); + buffer_put_cstring(&m, stdio_forward_host); + buffer_put_int(&m, stdio_forward_port); + + if (mux_client_write_packet(fd, &m) != 0) + fatal("%s: write packet: %s", __func__, strerror(errno)); + + /* Send the stdio file descriptors */ + if (mm_send_fd(fd, STDIN_FILENO) == -1 || + mm_send_fd(fd, STDOUT_FILENO) == -1) + fatal("%s: send fds failed", __func__); + + debug3("%s: stdio forward request sent", __func__); + + /* Read their reply */ + buffer_clear(&m); + + if (mux_client_read_packet(fd, &m) != 0) { + error("%s: read from master failed: %s", + __func__, strerror(errno)); + buffer_free(&m); + return -1; + } + + type = buffer_get_int(&m); + switch (type) { + case MUX_S_OK: + break; + case MUX_S_PERMISSION_DENIED: + e = buffer_get_string(&m, NULL); + buffer_free(&m); + fatal("Master refused forwarding request: %s", e); + case MUX_S_FAILURE: + e = buffer_get_string(&m, NULL); + buffer_free(&m); + fatal("%s: stdio forwarding request failed: %s", __func__, e); + default: + buffer_free(&m); + error("%s: unexpected response from master 0x%08x", + __func__, type); + return -1; + } + + signal(SIGHUP, control_client_sighandler); + signal(SIGINT, control_client_sighandler); + signal(SIGTERM, control_client_sighandler); + signal(SIGWINCH, control_client_sigrelay); + + /* + * Stick around until the controlee closes the client_fd. + */ + buffer_clear(&m); + if (mux_client_read_packet(fd, &m) != 0) { + if (errno == EPIPE || + (errno == EINTR && muxclient_terminate != 0)) + return 0; + fatal("%s: mux_client_read_packet: %s", + __func__, strerror(errno)); + } + type = buffer_get_int(&m); + if (type == MUX_S_FAILURE || type == MUX_S_PERMISSION_DENIED) { + e = buffer_get_string(&m, NULL); + fatal("%s: master returned error: %s", __func__, e); + } else + fatal("%s: master returned unexpected message %u", + __func__, type); +} + +/* Multiplex client main loop. */ +void +muxclient(const char *path) +{ + struct sockaddr_un addr; + int sock; + u_int pid; + + if (muxclient_command == 0) { + if (stdio_forward_host != NULL) + muxclient_command = SSHMUX_COMMAND_STDIO_FWD; + else + muxclient_command = SSHMUX_COMMAND_OPEN; + } + + switch (options.control_master) { + case SSHCTL_MASTER_AUTO: + case SSHCTL_MASTER_AUTO_ASK: + debug("auto-mux: Trying existing master"); + /* FALLTHROUGH */ + case SSHCTL_MASTER_NO: + break; + default: + return; + } + + memset(&addr, '\0', sizeof(addr)); + addr.sun_family = AF_UNIX; + addr.sun_len = offsetof(struct sockaddr_un, sun_path) + + strlen(path) + 1; + + if (strlcpy(addr.sun_path, path, + sizeof(addr.sun_path)) >= sizeof(addr.sun_path)) + fatal("ControlPath too long"); + + if ((sock = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) + fatal("%s socket(): %s", __func__, strerror(errno)); + + if (connect(sock, (struct sockaddr *)&addr, addr.sun_len) == -1) { + switch (muxclient_command) { + case SSHMUX_COMMAND_OPEN: + case SSHMUX_COMMAND_STDIO_FWD: + break; + default: + fatal("Control socket connect(%.100s): %s", path, + strerror(errno)); + } + if (errno == ENOENT) + debug("Control socket \"%.100s\" does not exist", path); + else { + error("Control socket connect(%.100s): %s", path, + strerror(errno)); + } + close(sock); + return; + } + set_nonblock(sock); + + if (mux_client_hello_exchange(sock) != 0) { + error("%s: master hello exchange failed", __func__); + close(sock); + return; + } + + switch (muxclient_command) { + case SSHMUX_COMMAND_ALIVE_CHECK: + if ((pid = mux_client_request_alive(sock)) == 0) + fatal("%s: master alive check failed", __func__); + fprintf(stderr, "Master running (pid=%d)\r\n", pid); + exit(0); + case SSHMUX_COMMAND_TERMINATE: + mux_client_request_terminate(sock); + fprintf(stderr, "Exit request sent.\r\n"); + exit(0); + case SSHMUX_COMMAND_OPEN: + if (mux_client_request_forwards(sock) != 0) { + error("%s: master forward request failed", __func__); + return; + } + mux_client_request_session(sock); + return; + case SSHMUX_COMMAND_STDIO_FWD: + mux_client_request_stdio_fwd(sock); + exit(0); + default: + fatal("unrecognised muxclient_command %d", muxclient_command); + } } Index: nchan.c =================================================================== RCS file: /cvs/src/usr.bin/ssh/nchan.c,v retrieving revision 1.62 diff -u -p -r1.62 nchan.c --- nchan.c 7 Nov 2008 18:50:18 -0000 1.62 +++ nchan.c 18 Jan 2010 04:20:35 -0000 @@ -159,7 +159,7 @@ chan_ibuf_empty(Channel *c) switch (c->istate) { case CHAN_INPUT_WAIT_DRAIN: if (compat20) { - if (!(c->flags & CHAN_CLOSE_SENT)) + if (!(c->flags & (CHAN_CLOSE_SENT|CHAN_LOCAL))) chan_send_eof2(c); chan_set_istate(c, CHAN_INPUT_CLOSED); } else { @@ -276,9 +276,12 @@ static void chan_rcvd_close2(Channel *c) { debug2("channel %d: rcvd close", c->self); - if (c->flags & CHAN_CLOSE_RCVD) - error("channel %d: protocol error: close rcvd twice", c->self); - c->flags |= CHAN_CLOSE_RCVD; + if (!(c->flags & CHAN_LOCAL)) { + if (c->flags & CHAN_CLOSE_RCVD) + error("channel %d: protocol error: close rcvd twice", + c->self); + c->flags |= CHAN_CLOSE_RCVD; + } if (c->type == SSH_CHANNEL_LARVAL) { /* tear down larval channels immediately */ chan_set_ostate(c, CHAN_OUTPUT_CLOSED); @@ -300,11 +303,13 @@ chan_rcvd_close2(Channel *c) chan_set_istate(c, CHAN_INPUT_CLOSED); break; case CHAN_INPUT_WAIT_DRAIN: - chan_send_eof2(c); + if (!(c->flags & CHAN_LOCAL)) + chan_send_eof2(c); chan_set_istate(c, CHAN_INPUT_CLOSED); break; } } + void chan_rcvd_eow(Channel *c) { @@ -452,6 +457,10 @@ chan_is_dead(Channel *c, int do_send) c->self, c->efd, buffer_len(&c->extended)); return 0; } + if (c->flags & CHAN_LOCAL) { + debug2("channel %d: is dead (local)", c->self); + return 1; + } if (!(c->flags & CHAN_CLOSE_SENT)) { if (do_send) { chan_send_close2(c); Index: ssh.c =================================================================== RCS file: /cvs/src/usr.bin/ssh/ssh.c,v retrieving revision 1.331 diff -u -p -r1.331 ssh.c --- ssh.c 11 Jan 2010 01:39:46 -0000 1.331 +++ ssh.c 18 Jan 2010 04:20:35 -0000 @@ -306,6 +306,11 @@ main(int ac, char **av) options.gateway_ports = 1; break; case 'O': + if (stdio_forward_host != NULL) + fatal("Cannot specify multiplexing " + "command with -W"); + else if (muxclient_command != 0) + fatal("Multiplexing command already specified"); if (strcmp(optarg, "check") == 0) muxclient_command = SSHMUX_COMMAND_ALIVE_CHECK; else if (strcmp(optarg, "exit") == 0) @@ -382,6 +387,10 @@ main(int ac, char **av) } break; case 'W': + if (stdio_forward_host != NULL) + fatal("stdio forward already specified"); + if (muxclient_command != 0) + fatal("Cannot specify stdio forward with -O"); if (parse_forward(&fwd, optarg, 1, 0)) { stdio_forward_host = fwd.listen_host; stdio_forward_port = fwd.listen_port; @@ -883,11 +892,18 @@ static int client_setup_stdio_fwd(const char *host_to_connect, u_short port_to_connect) { Channel *c; + int in, out; debug3("client_setup_stdio_fwd %s:%d", host_to_connect, port_to_connect); - if ((c = channel_connect_stdio_fwd(host_to_connect, port_to_connect)) - == NULL) + + in = dup(STDIN_FILENO); + out = dup(STDOUT_FILENO); + if (in < 0 || out < 0) + fatal("channel_connect_stdio_fwd: dup() in/out failed"); + + if ((c = channel_connect_stdio_fwd(host_to_connect, port_to_connect, + in, out)) == NULL) return 0; channel_register_cleanup(c->self, client_cleanup_stdio_fwd, 0); return 1; Index: PROTOCOL.mux =================================================================== RCS file: PROTOCOL.mux diff -N PROTOCOL.mux --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ PROTOCOL.mux 18 Jan 2010 04:20:36 -0000 @@ -0,0 +1,168 @@ +This document describes the multiplexing protocol used by ssh(1)'s +ControlMaster connection-sharing. + +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 + 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 server will then reply with MUX_S_OK, 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 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 + +The server replies with: + + uint32 MUX_S_ALIVE + uint32 server pid + +4. Remotely terminating a master + +A client may request that a master terminate immediately: + + uint32 MUX_C_TERMINATE + +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 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. + +5. Requesting closure of port forwards + +A client may request the master to establish a port forward: + + uint32 MUX_C_OPEN_FORWARD + 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 + 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. + +A server may reply with a MUX_S_OK, a MUX_S_PERMISSION_DENIED or a +MUX_S_FAILURE. + +7. Status messages + +The MUX_S_OK message is empty: + + uint32 MUX_S_OK + +The MUX_S_PERMISSION_DENIED and MUX_S_FAILURE include a reason: + + uint32 MUX_S_PERMISSION_DENIED + string reason + + uint32 MUX_S_FAILURE + 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_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$ From joachim at joachimschipper.nl Mon Jan 18 23:39:44 2010 From: joachim at joachimschipper.nl (Joachim Schipper) Date: Mon, 18 Jan 2010 13:39:44 +0100 Subject: Repost: [patch] Automatically add keys to agent In-Reply-To: <4B52F233.6030903@roumenpetrov.info> References: <20100112002433.GC27817@polymnia.jschipper.dynalias.net> <20100116193428.GA32743@polymnia.jschipper.dynalias.net> <4B52F233.6030903@roumenpetrov.info> Message-ID: <20100118123943.GB20756@thalia.joachimschipper.nl> On Sun, Jan 17, 2010 at 01:19:15PM +0200, Roumen Petrov wrote: > Joachim Schipper wrote: > >On Tue, Jan 12, 2010 at 01:24:34AM +0100, Joachim Schipper wrote: > >>My keys are secured with a passphrase. That's good for security, but > >>having to type the passphrase either at every login or at every > >>invocation of ssh(1) is annoying. > > > >>Hence, this patch. I'll just quote ssh_config(5): > >> > > AddKeyToAgent > > If this option is set to ``yes'' and ssh-agent(1) is running, any > > keys used will be added to the agent (with the default lifetime). > > Setting this to ``ask'' will cause ssh to require confirmation > > using the SSH_ASKPASS program before the key is added (see > > ssh-add(1) for details). The argument must be ``yes'', ``ask'', > > or ``no''. The default is ``no''. > > > >I am a bit disappointed by the total lack of response - does nobody else > >have this problem? I'm willing to do more work on it, if so desired, and > >I wouldn't mind having to wait until OpenBSD 4.7 is tagged if everyone's > >too busy right now. > > [SNIP] > Why to use this as I could use IdentitiesOnly and IdentityFile per > host as initially I could load all required keys info agent ? > May be instead new option you could enhance existing option > IdentitiesOnly with ask. I don't understand what you are saying, I'm afraid. What this patch does can be described as follows: Without: you at local$ ssh somehost Enter passphrase for RSA key 'foo': you at somehost$ exit $ ssh otherhost Enter passphrase for RSA key 'foo': you at otherhost$ With: you at local$ ssh somehost Enter passphrase for RSA key 'foo': you at somehost$ exit $ ssh otherhost you at otherhost$ That is, it means you don't have to type the passphrase twice. Of course, loading everything into the agent at login works, too, but that means you'll have to type your passphrase even if you log out/the keys expire/whatever before you get to use them. Joachim From joachim at joachimschipper.nl Mon Jan 18 23:54:42 2010 From: joachim at joachimschipper.nl (Joachim Schipper) Date: Mon, 18 Jan 2010 13:54:42 +0100 Subject: Repost: [patch] Automatically add keys to agent In-Reply-To: <20100116221547.GA26952@finestructure.net> References: <20100112002433.GC27817@polymnia.jschipper.dynalias.net> <20100116193428.GA32743@polymnia.jschipper.dynalias.net> <20100116221547.GA26952@finestructure.net> Message-ID: <20100118125441.GC20756@thalia.joachimschipper.nl> On Sat, Jan 16, 2010 at 05:15:47PM -0500, Jameson Rollins wrote: > On Sat, Jan 16, 2010 at 08:34:30PM +0100, Joachim Schipper wrote: > > On Tue, Jan 12, 2010 at 01:24:34AM +0100, Joachim Schipper wrote: > > > My keys are secured with a passphrase. That's good for security, but > > > having to type the passphrase either at every login or at every > > > invocation of ssh(1) is annoying. > > > > > Hence, this patch. I'll just quote ssh_config(5): > > > > > AddKeyToAgent > > If this option is set to ``yes'' and ssh-agent(1) is running, any > > keys used will be added to the agent (with the default lifetime). > > Setting this to ``ask'' will cause ssh to require confirmation > > using the SSH_ASKPASS program before the key is added (see > > ssh-add(1) for details). The argument must be ``yes'', ``ask'', > > or ``no''. The default is ``no''. > > > > I am a bit disappointed by the total lack of response - does nobody else > > have this problem? I'm willing to do more work on it, if so desired, and > > I wouldn't mind having to wait until OpenBSD 4.7 is tagged if everyone's > > too busy right now. > > I think probably everyone already has hooks or wrapper scripts they've > put together to accomplish this. For instance I have a proxycommand > that does it for me. That said, I think it's a pretty good idea. I > would rather use something like this than the hackish wrapper scripts > I'm currently using. > > That said, I wasn't a big fan of your dismissal of the ssh-add -c > option. I think that is a very important option that everyone should > be using. You should always want to be informed if anything is trying > to use your key. Otherwise a malicious program could gain access to > your key without your knowning it. You're right, it would be good to support it. I had some problems figuring out a decent UI for it, though. The best solution I could think of leads to configuration lines like "AddKeyToAgent ask confirm". Most directives are a lot simpler than that. Otherwise, how would you feel about "AutoAdd ask", "AutoAddRequireConfirmation yes"? (Better names would be welcome, obviously.) Finally, one could add a "default confirm" option to ssh-agent. I'm not sure that is a good idea, though: it has very little to do with my proposed change. Joachim From joachim at joachimschipper.nl Tue Jan 19 00:22:04 2010 From: joachim at joachimschipper.nl (Joachim Schipper) Date: Mon, 18 Jan 2010 14:22:04 +0100 Subject: Repost: [patch] Automatically add keys to agent In-Reply-To: <20100118125441.GC20756@thalia.joachimschipper.nl> References: <20100112002433.GC27817@polymnia.jschipper.dynalias.net> <20100116193428.GA32743@polymnia.jschipper.dynalias.net> <20100116221547.GA26952@finestructure.net> <20100118125441.GC20756@thalia.joachimschipper.nl> Message-ID: <20100118132204.GA28460@thalia.joachimschipper.nl> On Mon, Jan 18, 2010 at 01:54:42PM +0100, Joachim Schipper wrote: > On Sat, Jan 16, 2010 at 05:15:47PM -0500, Jameson Rollins wrote: > > On Sat, Jan 16, 2010 at 08:34:30PM +0100, Joachim Schipper wrote: > > > On Tue, Jan 12, 2010 at 01:24:34AM +0100, Joachim Schipper wrote: > > > > My keys are secured with a passphrase. That's good for security, but > > > > having to type the passphrase either at every login or at every > > > > invocation of ssh(1) is annoying. > > > > > > > Hence, this patch. I'll just quote ssh_config(5): > > > > > > > AddKeyToAgent > > > If this option is set to ``yes'' and ssh-agent(1) is running, any > > > keys used will be added to the agent (with the default lifetime). > > > Setting this to ``ask'' will cause ssh to require confirmation > > > using the SSH_ASKPASS program before the key is added (see > > > ssh-add(1) for details). The argument must be ``yes'', ``ask'', > > > or ``no''. The default is ``no''. > > (...) I wasn't a big fan of your dismissal of the ssh-add -c > > option. I think that is a very important option that everyone should > > be using. You should always want to be informed if anything is trying > > to use your key. Otherwise a malicious program could gain access to > > your key without your knowning it. > > You're right, it would be good to support it. I had some problems > figuring out a decent UI for it, though. > > The best solution I could think of leads to configuration lines like > "AddKeyToAgent ask confirm". Most directives are a lot simpler than > that. It just hit me that "AddKeyToAgent yes/confirm/ask/no" should work. "Confirm" would add the key without asking, but require confirmation before each use. After all, asking whether the key should be added *and* confirming each use seems excessive. I'll roll a new patch with this change. Joachim From openssh at roumenpetrov.info Tue Jan 19 07:23:34 2010 From: openssh at roumenpetrov.info (Roumen Petrov) Date: Mon, 18 Jan 2010 22:23:34 +0200 Subject: Repost: [patch] Automatically add keys to agent In-Reply-To: <20100118123943.GB20756@thalia.joachimschipper.nl> References: <20100112002433.GC27817@polymnia.jschipper.dynalias.net> <20100116193428.GA32743@polymnia.jschipper.dynalias.net> <4B52F233.6030903@roumenpetrov.info> <20100118123943.GB20756@thalia.joachimschipper.nl> Message-ID: <4B54C346.8030803@roumenpetrov.info> Joachim Schipper wrote: > On Sun, Jan 17, 2010 at 01:19:15PM +0200, Roumen Petrov wrote: >> Joachim Schipper wrote: >>> On Tue, Jan 12, 2010 at 01:24:34AM +0100, Joachim Schipper wrote: >>>> My keys are secured with a passphrase. That's good for security, but >>>> having to type the passphrase either at every login or at every >>>> invocation of ssh(1) is annoying. >>> >>>> Hence, this patch. I'll just quote ssh_config(5): >>>> >>> AddKeyToAgent >>> If this option is set to ``yes'' and ssh-agent(1) is running, any >>> keys used will be added to the agent (with the default lifetime). >>> Setting this to ``ask'' will cause ssh to require confirmation >>> using the SSH_ASKPASS program before the key is added (see >>> ssh-add(1) for details). The argument must be ``yes'', ``ask'', >>> or ``no''. The default is ``no''. >>> >>> I am a bit disappointed by the total lack of response - does nobody else >>> have this problem? I'm willing to do more work on it, if so desired, and >>> I wouldn't mind having to wait until OpenBSD 4.7 is tagged if everyone's >>> too busy right now. >> >> [SNIP] >> Why to use this as I could use IdentitiesOnly and IdentityFile per >> host as initially I could load all required keys info agent ? >> May be instead new option you could enhance existing option >> IdentitiesOnly with ask. > > I don't understand what you are saying, I'm afraid. > > What this patch does can be described as follows: > > Without: > you at local$ ssh somehost > Enter passphrase for RSA key 'foo': > you at somehost$ exit > $ ssh otherhost > Enter passphrase for RSA key 'foo': > you at otherhost$ > > With: > you at local$ ssh somehost > Enter passphrase for RSA key 'foo': > you at somehost$ exit > $ ssh otherhost > you at otherhost$ > > That is, it means you don't have to type the passphrase twice. And what is difference it option IdentitiesOnly is enhanced to add key to agent ? With current behavior user will be prompted to password if key is not in agent, but if key is loaded client will try to use it. > Of course, loading everything into the agent at login works, too, but > that means you'll have to type your passphrase even if you log out/the > keys expire/whatever before you get to use them. > > Joachim Roumen From joachim at joachimschipper.nl Tue Jan 19 11:30:34 2010 From: joachim at joachimschipper.nl (Joachim Schipper) Date: Tue, 19 Jan 2010 01:30:34 +0100 Subject: [patch] Automatically add keys to agent In-Reply-To: References: <20100112002433.GC27817@polymnia.jschipper.dynalias.net> <20100116193428.GA32743@polymnia.jschipper.dynalias.net> Message-ID: <20100119003031.GA19331@polymnia.jschipper.dynalias.net> On Sun, Jan 17, 2010 at 11:28:59AM +1100, Damien Miller wrote: > On Sat, 16 Jan 2010, Joachim Schipper wrote: > > AddKeyToAgent > > If this option is set to ``yes'' and ssh-agent(1) is running, any > > keys used will be added to the agent (with the default lifetime). > > Setting this to ``ask'' will cause ssh to require confirmation > > using the SSH_ASKPASS program before the key is added (see > > ssh-add(1) for details). The argument must be ``yes'', ``ask'', > > or ``no''. The default is ``no''. > > Please file it at https://bugzilla.mindrot.org/ so it doesn't get lost. > We can take a look at it after OpenSSH-5.4 ships. I filed a new and enhanced version (it now adds all keys which can be unlocked with the passphrase) as Bug 1699, https://bugzilla.mindrot.org/show_bug.cgi?id=1699. I'll be happy to make any requested changes. But yeah, getting the new release out should take priority - this is just a convenience feature. Joachim From mouring at eviladmin.org Tue Jan 19 12:12:16 2010 From: mouring at eviladmin.org (Ben Lindstrom) Date: Mon, 18 Jan 2010 19:12:16 -0600 Subject: [patch] Automatically add keys to agent In-Reply-To: <20100119003031.GA19331@polymnia.jschipper.dynalias.net> References: <20100112002433.GC27817@polymnia.jschipper.dynalias.net> <20100116193428.GA32743@polymnia.jschipper.dynalias.net> <20100119003031.GA19331@polymnia.jschipper.dynalias.net> Message-ID: <94A7ADE4-70C8-4604-8DA3-3E172F867D47@eviladmin.org> On Jan 18, 2010, at 6:30 PM, Joachim Schipper wrote: > On Sun, Jan 17, 2010 at 11:28:59AM +1100, Damien Miller wrote: >> On Sat, 16 Jan 2010, Joachim Schipper wrote: >>> AddKeyToAgent >>> If this option is set to ``yes'' and ssh-agent(1) is running, any >>> keys used will be added to the agent (with the default lifetime). >>> Setting this to ``ask'' will cause ssh to require confirmation >>> using the SSH_ASKPASS program before the key is added (see >>> ssh-add(1) for details). The argument must be ``yes'', ``ask'', >>> or ``no''. The default is ``no''. >> >> Please file it at https://bugzilla.mindrot.org/ so it doesn't get lost. >> We can take a look at it after OpenSSH-5.4 ships. > > I filed a new and enhanced version (it now adds all keys which can be > unlocked with the passphrase) as Bug 1699, > https://bugzilla.mindrot.org/show_bug.cgi?id=1699. > > I'll be happy to make any requested changes. But yeah, getting the new > release out should take priority - this is just a convenience feature. You may want to skim the Apple patches. They have this functionality included in MacOS X. - Ben From yingyuan.cheng at gmail.com Fri Jan 22 12:18:51 2010 From: yingyuan.cheng at gmail.com (yingyuan cheng) Date: Fri, 22 Jan 2010 09:18:51 +0800 Subject: Is there any way to hook the point when channel port listener accepts a new connection? Message-ID: <1c2183ae1001211718t7c962cb8w894e59939a09bdb3@mail.gmail.com> After an user is authenticated, he should be authenticated again by his interactive shell before starting a tunnel for him. How can I fulfill this task? I think I should add a hook when a listening channel accepts a new connection. Is there easier way? Thanks. leo From peter at stuge.se Fri Jan 22 17:24:35 2010 From: peter at stuge.se (Peter Stuge) Date: Fri, 22 Jan 2010 07:24:35 +0100 Subject: Is there any way to hook the point when channel port listener accepts a new connection? In-Reply-To: <1c2183ae1001211718t7c962cb8w894e59939a09bdb3@mail.gmail.com> References: <1c2183ae1001211718t7c962cb8w894e59939a09bdb3@mail.gmail.com> Message-ID: <20100122062435.3450.qmail@stuge.se> yingyuan cheng wrote: > After an user is authenticated, he should be authenticated again by > his interactive shell before starting a tunnel for him. How can I > fulfill this task? I think I should add a hook when a listening > channel accepts a new connection. That doesn't work. There may not be a shell when a port forward channel request comes. > Is there easier way? The SSH protocol doesn't allow extra authentication once the initial authentication has succeeded. Since a port forward is implemented in the SSH protocol and has nothing to do with interactive shells you have to rely only on what is offered by the protocol. It seems that you control the server side here, so you could add an optional kbd-int authentication to be performed at initial login, and add code to test the result of this auth when the port forward channel open request comes. I know it's not perfect but I suspect it's the best you can do. :\ //Peter From yingyuan.cheng at gmail.com Fri Jan 22 18:15:15 2010 From: yingyuan.cheng at gmail.com (yingyuan cheng) Date: Fri, 22 Jan 2010 15:15:15 +0800 Subject: Is there any way to hook the point when channel port listener accepts a new connection? In-Reply-To: <20100122062435.3450.qmail@stuge.se> References: <1c2183ae1001211718t7c962cb8w894e59939a09bdb3@mail.gmail.com> <20100122062435.3450.qmail@stuge.se> Message-ID: <1c2183ae1001212315n399121d1p7cd5814278d176b9@mail.gmail.com> Thank you Peter. To correlate forwarding channel to login shell, sshd may provide user an external optional command, executed when the port forward channel receives a forwarding request. If the external command exits with status 0, the request can go on, else forbide the request. So I can configure system account shell with my customized program, which directs the forwarding channel. Is it difficult to do so if I modify sshd source? Best Regards, Leo 2010/1/22 Peter Stuge : > It seems that you control the server side here, so you could add an > optional kbd-int authentication to be performed at initial login, and > add code to test the result of this auth when the port forward > channel open request comes. I know it's not perfect but I suspect > it's the best you can do. :\ > > > //Peter From peter at stuge.se Fri Jan 22 18:22:33 2010 From: peter at stuge.se (Peter Stuge) Date: Fri, 22 Jan 2010 08:22:33 +0100 Subject: Is there any way to hook the point when channel port listener accepts a new connection? In-Reply-To: <1c2183ae1001212315n399121d1p7cd5814278d176b9@mail.gmail.com> References: <1c2183ae1001211718t7c962cb8w894e59939a09bdb3@mail.gmail.com> <20100122062435.3450.qmail@stuge.se> <1c2183ae1001212315n399121d1p7cd5814278d176b9@mail.gmail.com> Message-ID: <20100122072233.11781.qmail@stuge.se> yingyuan cheng wrote: > To correlate forwarding channel to login shell, The point I tried to make is that you can not expect any login shell to exist when the request for the forwarding channel arrives. //Peter From yingyuan.cheng at gmail.com Fri Jan 22 18:32:02 2010 From: yingyuan.cheng at gmail.com (yingyuan cheng) Date: Fri, 22 Jan 2010 15:32:02 +0800 Subject: Is there any way to hook the point when channel port listener accepts a new connection? In-Reply-To: <20100122072233.11781.qmail@stuge.se> References: <1c2183ae1001211718t7c962cb8w894e59939a09bdb3@mail.gmail.com> <20100122062435.3450.qmail@stuge.se> <1c2183ae1001212315n399121d1p7cd5814278d176b9@mail.gmail.com> <20100122072233.11781.qmail@stuge.se> Message-ID: <1c2183ae1001212332i18361135t7d5cf879f5950634@mail.gmail.com> If no login shell existing when a forwarding request coming, is it possible to ignore or close the coming request? I want to use one system account to provide tunnels for hundreds of users, how can I make things easier? Thanks. 2010/1/22 Peter Stuge : > yingyuan cheng wrote: >> To correlate forwarding channel to login shell, > > The point I tried to make is that you can not expect any login shell > to exist when the request for the forwarding channel arrives. > > > //Peter > _______________________________________________ > openssh-unix-dev mailing list > openssh-unix-dev at mindrot.org > https://lists.mindrot.org/mailman/listinfo/openssh-unix-dev > From vinschen at redhat.com Fri Jan 22 20:40:39 2010 From: vinschen at redhat.com (Corinna Vinschen) Date: Fri, 22 Jan 2010 10:40:39 +0100 Subject: [PATCH] Cygwin: set SSH_IOBUFSZ to 65535 Message-ID: <20100122094039.GL2402@calimero.vinschen.de> Hi, could somebody with checkin rights please apply the below patch? A while back we set SSH_IOBUFSZ, the size of the IO buffers used by ssh, to 65536 for Cygwin, which resulted in a noticable speed up of the connection. However, due to constraints in Windows Sockets in terms of socket inheritance, we had to reduce the default SO_RCVBUF/SO_SNDBUF buffer size in Cygwin from 65536 to 65535. The below patch sets this size for the ssh buffers as well. The match between application buffer and Windows socket buffers results in the best throughput. Thanks, Corinna Index: configure.ac =================================================================== RCS file: /cvs/openssh/configure.ac,v retrieving revision 1.438 diff -u -p -r1.438 configure.ac --- configure.ac 18 Jan 2010 01:05:39 -0000 1.438 +++ configure.ac 22 Jan 2010 09:35:57 -0000 @@ -446,7 +446,7 @@ int main(void) { exit(0); } AC_DEFINE(DISABLE_FD_PASSING, 1, [Define if your platform needs to skip post auth file descriptor passing]) - AC_DEFINE(SSH_IOBUFSZ, 65536, [Windows is sensitive to read buffer size]) + AC_DEFINE(SSH_IOBUFSZ, 65535, [Windows is sensitive to read buffer size]) ;; *-*-dgux*) AC_DEFINE(IP_TOS_IS_BROKEN, 1, -- Corinna Vinschen Cygwin Project Co-Leader Red Hat From alex at alex.org.uk Fri Jan 22 21:15:33 2010 From: alex at alex.org.uk (Alex Bligh) Date: Fri, 22 Jan 2010 10:15:33 +0000 Subject: Is there any way to hook the point when channel port listener accepts a new connection? In-Reply-To: <1c2183ae1001212332i18361135t7d5cf879f5950634@mail.gmail.com> References: <1c2183ae1001211718t7c962cb8w894e59939a09bdb3@mail.gmail.com> <20100122062435.3450.qmail@stuge.se> <1c2183ae1001212315n399121d1p7cd5814278d176b9@mail.gmail.com> <20100122072233.11781.qmail@stuge.se> <1c2183ae1001212332i18361135t7d5cf879f5950634@mail.gmail.com> Message-ID: <74C3A21052A4C5B3A199791B@nimrod.local> --On 22 January 2010 15:32:02 +0800 yingyuan cheng wrote: > If no login shell existing when a forwarding request coming, is it > possible to ignore or close the coming request? > > I want to use one system account to provide tunnels for hundreds of > users, how can I make things easier? I think the easiest way to do this is to give them all the same system account (UID), but to give them all different public keys. You can then use the authenticate by key stuff to present different parameters. You can disable any sort of interactive login, shell, ability to run commands etc., and just allow them to tunnel. I've got this working with tunnels before. The main problem if you are using tun device tunnels is that you often want a script to set them up, and it is near impossible to get the server tun device name when they log in from a (fixed) script you run. I posted a patch here to put this in an environment variable. -- Alex Bligh From brian at interlinx.bc.ca Sat Jan 23 03:50:22 2010 From: brian at interlinx.bc.ca (Brian J. Murrell) Date: Fri, 22 Jan 2010 16:50:22 +0000 (UTC) Subject: moving X11 portforwarding out into a "plugin" framework Message-ID: I think everyone will admit that X11 forwarding has been an incredible feature in [open]ssh. X11 is not the only local->remote protocol that might be useful across an SSH session however. But having to hack the code for new protocols as they come around seems silly. Wouldn't it be more useful to be able to describe a protocol that needs forwarding and some configuration that might need doing on each end outside of the source code? Then as new applications come along that could take advantage of port forward- ing can "plug in" to this system and get their protocols forwarded across X without having to hack the openssh code and push it upstream, etc. I would imagine on the local side, the ssh client would need to be able to forward into unix and ipv4/6 sockets and on the remote side one would need to be able to possibly create sockets and/or set environment variables, etc. Thots? b. From tim at multitalents.net Sat Jan 23 05:26:39 2010 From: tim at multitalents.net (Tim Rice) Date: Fri, 22 Jan 2010 10:26:39 -0800 (PST) Subject: [PATCH] Cygwin: set SSH_IOBUFSZ to 65535 In-Reply-To: <20100122094039.GL2402@calimero.vinschen.de> References: <20100122094039.GL2402@calimero.vinschen.de> Message-ID: On Fri, 22 Jan 2010, Corinna Vinschen wrote: > Hi, > > > could somebody with checkin rights please apply the below patch? Patch applied. > - AC_DEFINE(SSH_IOBUFSZ, 65536, [Windows is sensitive to read buffer size]) > + AC_DEFINE(SSH_IOBUFSZ, 65535, [Windows is sensitive to read buffer size]) -- Tim Rice Multitalents (707) 887-1469 tim at multitalents.net From peter at stuge.se Sat Jan 23 15:04:02 2010 From: peter at stuge.se (Peter Stuge) Date: Sat, 23 Jan 2010 05:04:02 +0100 Subject: moving X11 portforwarding out into a "plugin" framework In-Reply-To: References: Message-ID: <20100123040402.20946.qmail@stuge.se> Brian J. Murrell wrote: > X11 is not the only local->remote protocol that might be useful > across an SSH session however. Besides TCP it's the only one specified by the standards though. //Peter From brian at interlinx.bc.ca Sun Jan 24 02:57:54 2010 From: brian at interlinx.bc.ca (Brian J. Murrell) Date: Sat, 23 Jan 2010 15:57:54 +0000 (UTC) Subject: moving X11 portforwarding out into a " plugin" framework References: <20100123040402.20946.qmail@stuge.se> Message-ID: Peter Stuge stuge.se> writes: > > Besides TCP it's the only one specified by the standards though. Which standards? Does/should a standard have to be written in order to forward additional protocols over the SSH session? From peter at stuge.se Sun Jan 24 03:04:32 2010 From: peter at stuge.se (Peter Stuge) Date: Sat, 23 Jan 2010 17:04:32 +0100 Subject: moving X11 portforwarding out into a " plugin" framework In-Reply-To: References: <20100123040402.20946.qmail@stuge.se> Message-ID: <20100123160432.18350.qmail@stuge.se> Brian J. Murrell wrote: > > Besides TCP it's the only one specified by the standards though. > > Which standards? RFC4250-4255, specifically 4254. > Does/should a standard have to be written in order to forward > additional protocols over the SSH session? Since the existing special-purpose SSH channels are standardized I think it would be a good idea. That said it might be possible to use local-style names (@your.domain) also for channels, like for other parts of the protocol. //Peter From alex at alex.org.uk Sun Jan 24 05:17:29 2010 From: alex at alex.org.uk (Alex Bligh) Date: Sat, 23 Jan 2010 18:17:29 +0000 Subject: moving X11 portforwarding out into a " plugin" framework In-Reply-To: <20100123160432.18350.qmail@stuge.se> References: <20100123040402.20946.qmail@stuge.se> <20100123160432.18350.qmail@stuge.se> Message-ID: <557AB64093762876B19F6035@Ximines.local> --On 23 January 2010 17:04:32 +0100 Peter Stuge wrote: >> Does/should a standard have to be written in order to forward >> additional protocols over the SSH session? > > Since the existing special-purpose SSH channels are standardized I > think it would be a good idea. Really? Is -w / tun device standardized? The only docs I could find were in the source. -- Alex Bligh From djm at mindrot.org Sun Jan 24 07:14:31 2010 From: djm at mindrot.org (Damien Miller) Date: Sun, 24 Jan 2010 07:14:31 +1100 (EST) Subject: moving X11 portforwarding out into a "plugin" framework In-Reply-To: References: Message-ID: On Fri, 22 Jan 2010, Brian J. Murrell wrote: > I think everyone will admit that X11 forwarding has been an incredible > feature in [open]ssh. X11 is not the only local->remote protocol that > might be useful across an SSH session however. > > But having to hack the code for new protocols as they come around > seems silly. Wouldn't it be more useful to be able to describe a > protocol that needs forwarding and some configuration that might need > doing on each end outside of the source code? > > Then as new applications come along that could take advantage of port > forward- ing can "plug in" to this system and get their protocols > forwarded across X without having to hack the openssh code and push it > upstream, etc. > > I would imagine on the local side, the ssh client would need to be > able to forward into unix and ipv4/6 sockets and on the remote side > one would need to be able to possibly create sockets and/or set > environment variables, etc. You should be able to do most of what you want using a Subsystem (see sshd_config) and a helper program on the client side. -d From dtucker at zip.com.au Sun Jan 24 09:11:56 2010 From: dtucker at zip.com.au (Darren Tucker) Date: Sun, 24 Jan 2010 09:11:56 +1100 Subject: moving X11 portforwarding out into a " plugin" framework In-Reply-To: <557AB64093762876B19F6035@Ximines.local> References: <20100123040402.20946.qmail@stuge.se> <20100123160432.18350.qmail@stuge.se> <557AB64093762876B19F6035@Ximines.local> Message-ID: <4B5B742C.1030405@zip.com.au> Alex Bligh wrote: > Really? Is -w / tun device standardized? The only docs I could > find were in the source. tun is a vendor extension: http://www.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL?rev=HEAD -- Darren Tucker (dtucker at zip.com.au) GPG key 8FF4FA69 / D9A3 86E9 7EEE AF4B B2D4 37C9 C982 80C7 8FF4 FA69 Good judgement comes with experience. Unfortunately, the experience usually comes from bad judgement. From alex at alex.org.uk Sun Jan 24 18:14:13 2010 From: alex at alex.org.uk (Alex Bligh) Date: Sun, 24 Jan 2010 07:14:13 +0000 Subject: moving X11 portforwarding out into a " plugin" framework In-Reply-To: <4B5B742C.1030405@zip.com.au> References: <20100123040402.20946.qmail@stuge.se> <20100123160432.18350.qmail@stuge.se> <557AB64093762876B19F6035@Ximines.local> <4B5B742C.1030405@zip.com.au> Message-ID: <230347413AB333CFD1770079@nimrod.local> --On 24 January 2010 09:11:56 +1100 Darren Tucker wrote: >> Really? Is -w / tun device standardized? The only docs I could >> find were in the source. > > tun is a vendor extension: > http://www.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL?rev=HEAD That's what I thought. And there is no reason in principle why other tunneling technologies shouldn't also be vendor extensions, as opposed to standards (not that I quite understand what Brian is trying to do). -- Alex Bligh From djm at mindrot.org Sun Jan 24 21:15:38 2010 From: djm at mindrot.org (Damien Miller) Date: Sun, 24 Jan 2010 21:15:38 +1100 (EST) Subject: ssh(1) multiplexing rewrite In-Reply-To: References: <4B4F490C.70006@yahoo.com> Message-ID: On Mon, 18 Jan 2010, Damien Miller wrote: > > 3) allow to run the mux server over SSH stdin instead of over a named > > Unix socket. That would be useful to embed ssh inside another program. > > > > For instance, Net::OpenSSH internally starts a new ssh in master mode > > and then sends commands to the remote machine through the mux socket > > running slave ssh processes, one per command. > > > > Handling the named Unix socket is a nuisance because it means > > accessing the file system, looking for a proper location to place the > > socket, checking that permissions are right, avoiding collisions with > > other instances of the module concurrently running and cleaning up. > > > > To add this feature to OpenSSH, besides allowing attaching the mux > > server to stdio, the protocol should also be modified in order to > > allow interleaving requests and responses related to different > > channels over the same mux stream, for instance including some session > > ID. > > Yes, that sounds reasonable. I'm not afraid to make incompatible changes > to this protocol and adding interleaved requests isn't so hard now. I'm hoping to commit the attached version soon. It adds request identifiers so supporting multiple sessions opened by one control connection should be possible without an incompatible protocol change in the future (though the server doesn't support it now). I'd appreciate your thoughts on whether the proposed protocol (documented in PROTOCOL.mux) meets your needs. -d -------------- next part -------------- Index: channels.c =================================================================== RCS file: /cvs/src/usr.bin/ssh/channels.c,v retrieving revision 1.301 diff -u -p -r1.301 channels.c --- channels.c 11 Jan 2010 01:39:46 -0000 1.301 +++ channels.c 24 Jan 2010 10:11:57 -0000 @@ -235,7 +235,6 @@ channel_register_fds(Channel *c, int rfd 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; @@ -323,6 +322,9 @@ channel_new(char *ctype, int type, int r 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->delayed = 1; /* prevent call to channel_post handler */ TAILQ_INIT(&c->status_confirms); debug("channel %d: new [%s]", found, remote_name); @@ -365,11 +367,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); @@ -395,8 +396,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); @@ -518,6 +517,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: @@ -531,6 +531,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: @@ -562,6 +563,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: @@ -612,6 +615,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: @@ -622,12 +627,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: @@ -834,9 +839,6 @@ channel_pre_open(Channel *c, fd_set *rea 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 */ @@ -981,6 +983,28 @@ channel_pre_x11_open(Channel *c, fd_set } } +static void +channel_pre_mux_client(Channel *c, fd_set *readset, fd_set *writeset) +{ + if (c->istate == CHAN_INPUT_OPEN && + 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 @@ -1213,19 +1237,14 @@ channel_decode_socks5(Channel *c, fd_set } Channel * -channel_connect_stdio_fwd(const char *host_to_connect, u_short port_to_connect) +channel_connect_stdio_fwd(const char *host_to_connect, u_short port_to_connect, + int in, int out) { Channel *c; - int in, out; debug("channel_connect_stdio_fwd %s:%d", host_to_connect, port_to_connect); - in = dup(STDIN_FILENO); - out = dup(STDOUT_FILENO); - if (in < 0 || out < 0) - fatal("channel_connect_stdio_fwd: dup() in/out failed"); - c = channel_new("stdio-forward", SSH_CHANNEL_OPENING, in, out, -1, CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, 0, "stdio-forward", /*nonblock*/0); @@ -1728,34 +1747,6 @@ channel_handle_efd(Channel *c, fd_set *r /* 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)) - 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) { if (c->type == SSH_CHANNEL_OPEN && @@ -1785,10 +1776,131 @@ channel_post_open(Channel *c, fd_set *re 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 && 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) @@ -1817,6 +1929,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; @@ -1825,6 +1939,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 Index: channels.h =================================================================== RCS file: /cvs/src/usr.bin/ssh/channels.h,v retrieving revision 1.102 diff -u -p -r1.102 channels.h --- channels.h 11 Jan 2010 01:39:46 -0000 1.102 +++ channels.h 24 Jan 2010 10:11:57 -0000 @@ -53,7 +53,9 @@ #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; @@ -81,6 +83,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,7 +97,7 @@ 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 client_tty; /* (client) TTY has been requested */ int force_drain; /* force close on iEOF */ @@ -141,6 +146,10 @@ 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; }; #define CHAN_EXTENDED_IGNORE 0 @@ -171,6 +180,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 @@ -242,7 +252,7 @@ void channel_clear_adm_permitted_opens( 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); +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: clientloop.c =================================================================== RCS file: /cvs/src/usr.bin/ssh/clientloop.c,v retrieving revision 1.216 diff -u -p -r1.216 clientloop.c --- clientloop.c 9 Jan 2010 05:04:24 -0000 1.216 +++ clientloop.c 24 Jan 2010 10:11:57 -0000 @@ -113,7 +113,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 @@ -138,7 +138,7 @@ static volatile sig_atomic_t received_si static int in_non_blocking_mode = 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. */ @@ -556,9 +556,6 @@ client_wait_until_can_do_something(fd_se 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 @@ -686,7 +683,7 @@ client_status_confirm(int type, Channel /* 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", @@ -830,6 +827,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 */ @@ -927,7 +925,7 @@ process_escapes(Channel *c, Buffer *bin, 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; @@ -937,7 +935,7 @@ process_escapes(Channel *c, Buffer *bin, 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 " @@ -982,7 +980,7 @@ process_escapes(Channel *c, Buffer *bin, continue; case '&': - if (c && c->ctl_fd != -1) + if (c && c->ctl_chan != -1) goto noescape; /* * Detach the program (continue to serve @@ -1033,7 +1031,7 @@ process_escapes(Channel *c, Buffer *bin, 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\ @@ -1082,7 +1080,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; @@ -1315,8 +1313,6 @@ client_loop(int have_pty, int escape_cha 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 */ @@ -1434,12 +1430,6 @@ client_loop(int have_pty, int escape_cha /* 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; @@ -1841,9 +1831,8 @@ client_input_channel_req(int type, u_int chan_rcvd_eow(c); } else if (strcmp(rtype, "exit-status") == 0) { exitval = packet_get_int(); - if (c->ctl_fd != -1) { - /* Dispatch to mux client */ - atomicio(vwrite, c->ctl_fd, &exitval, sizeof(exitval)); + if (c->ctl_chan != -1) { + mux_exit_message(c, exitval); success = 1; } else if (id == session_ident) { /* Record exit value of local session */ Index: clientloop.h =================================================================== RCS file: /cvs/src/usr.bin/ssh/clientloop.h,v retrieving revision 1.22 diff -u -p -r1.22 clientloop.h --- clientloop.h 12 Jun 2008 15:19:17 -0000 1.22 +++ clientloop.h 24 Jan 2010 10:11:57 -0000 @@ -56,18 +56,14 @@ typedef void global_confirm_cb(int, u_in 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) */ void muxserver_listen(void); -int muxserver_accept_control(void); void muxclient(const char *); +void mux_exit_message(Channel *, int); Index: mux.c =================================================================== RCS file: /cvs/src/usr.bin/ssh/mux.c,v retrieving revision 1.9 diff -u -p -r1.9 mux.c --- mux.c 9 Jan 2010 05:04:24 -0000 1.9 +++ mux.c 24 Jan 2010 10:11:57 -0000 @@ -17,21 +17,19 @@ /* ssh session multiplexing support */ +// XXX signal of slave passed to master + /* * 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 @@ -43,6 +41,7 @@ #include #include +#include #include #include #include @@ -53,6 +52,7 @@ #include #include +#include "atomicio.h" #include "xmalloc.h" #include "log.h" #include "ssh.h" @@ -77,13 +77,16 @@ extern int stdin_null_flag; extern char *host; 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; @@ -93,6 +96,9 @@ struct mux_session_confirm_ctx { /* 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; @@ -102,268 +108,226 @@ static volatile sig_atomic_t muxclient_t /* 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; - - 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.sun_len = offsetof(struct sockaddr_un, sun_path) + - strlen(options.control_path) + 1; +struct mux_master_state { + int hello_rcvd; +}; - if (strlcpy(addr.sun_path, options.control_path, - sizeof(addr.sun_path)) >= sizeof(addr.sun_path)) - fatal("ControlPath too long"); +/* 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 + +/* 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, 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 } +}; - if ((muxserver_sock = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) - fatal("%s socket(): %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); - old_umask = umask(0177); - if (bind(muxserver_sock, (struct sockaddr *)&addr, addr.sun_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)); + 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 at 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); + Channel *sc, *c = channel_by_id(cid); - 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); + 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) + debug2("%s: channel %d n session channel %d", + __func__, c->self, c->remote_id); + c->remote_id = -1; + sc->ctl_chan = -1; + chan_mark_dead(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; - } - if ((ver = buffer_get_char(&m)) != SSHMUX_VER) { - error("%s: wrong client version %d", __func__, ver); - buffer_free(&m); - close(client_fd); - return 0; - } + return 0; +} - allowed = 1; - mux_command = buffer_get_int(&m); - flags = buffer_get_int(&m); +/* Mux master protocol message handlers */ - buffer_clear(&m); +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; - 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 (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 (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); + + /* 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); + + 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; +} - /* 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; - } +static int +process_mux_new_session(u_int rid, Channel *c, Buffer *m, Buffer *r) +{ + Channel *nc; + struct mux_session_confirm_ctx *cctx; + char *cmd, *cp; + u_int i, j, len, env_len, escape_char, window, packetmax; + int new_fd[3]; - if (!allowed) { - error("Refused control connection"); - close(client_fd); - buffer_free(&m); - return 0; + /* Reply for SSHMUX_COMMAND_OPEN */ + cctx = xcalloc(1, sizeof(*cctx)); + cctx->term = NULL; + cmd = NULL; + if (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 (cctx->term != NULL) + xfree(cctx->term); + error("%s: malformed message", __func__); + return -1; } - 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; + 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; + } } - 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); + 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); - 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); /* 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++) @@ -374,37 +338,56 @@ muxserver_accept_control(void) xfree(cctx->env); xfree(cctx->term); buffer_free(&cctx->cmd); - close(client_fd); xfree(cctx); - return 0; + + /* prepare reply */ + buffer_put_int(r, MUX_S_FAILURE); + buffer_put_int(r, rid); + buffer_put_cstring(r, + "did not receive file descriptors"); + return -1; } } - debug2("%s: got fds stdin %d, stdout %d, stderr %d", __func__, + debug3("%s: got fds stdin %d, stdout %d, stderr %d", __func__, new_fd[0], new_fd[1], new_fd[2]); - /* Try to pick up ttymodes from client before it goes raw */ - if (cctx->want_tty && tcgetattr(new_fd[0], &cctx->tio) == -1) - error("%s: tcgetattr: %s", __func__, strerror(errno)); - - /* This roundtrip is just for synchronisation of ttymodes */ - buffer_clear(&m); - if (ssh_msg_send(client_fd, SSHMUX_VER, &m) == -1) { - error("%s: client msg_send failed", __func__); - close(client_fd); + /* XXX support multiple child sessions in future */ + if (c->remote_id != -1) { + debug2("%s: session already open", __func__); + /* prepare reply */ + buffer_put_int(r, MUX_S_FAILURE); + buffer_put_int(r, rid); + buffer_put_cstring(r, "Multiple sessions not supported"); + cleanup: close(new_fd[0]); close(new_fd[1]); close(new_fd[2]); - buffer_free(&m); xfree(cctx->term); if (env_len != 0) { for (i = 0; i < env_len; i++) xfree(cctx->env[i]); xfree(cctx->env); } + buffer_free(&cctx->cmd); return 0; } - buffer_free(&m); + + if (options.control_master == SSHCTL_MASTER_ASK || + options.control_master == SSHCTL_MASTER_AUTO_ASK) { + if (!ask_permission("Allow shared connection to %s? ", host)) { + debug2("%s: session refused by user", __func__); + /* prepare reply */ + buffer_put_int(r, MUX_S_PERMISSION_DENIED); + buffer_put_int(r, rid); + buffer_put_cstring(r, "Permission denied"); + goto cleanup; + } + } + + /* Try to pick up ttymodes from client before it goes raw */ + if (cctx->want_tty && tcgetattr(new_fd[0], &cctx->tio) == -1) + error("%s: tcgetattr: %s", __func__, strerror(errno)); /* enable nonblocking unless tty */ if (!isatty(new_fd[0])) @@ -414,257 +397,1071 @@ muxserver_accept_control(void) if (!isatty(new_fd[2])) set_nonblock(new_fd[2]); - set_nonblock(client_fd); - window = CHAN_SES_WINDOW_DEFAULT; packetmax = CHAN_SES_PACKET_DEFAULT; if (cctx->want_tty) { window >>= 1; packetmax >>= 1; } - - c = channel_new("session", SSH_CHANNEL_OPENING, + + nc = channel_new("session", SSH_CHANNEL_OPENING, new_fd[0], new_fd[1], new_fd[2], window, packetmax, CHAN_EXTENDED_WRITE, "client-session", /*nonblock*/0); - c->ctl_fd = client_fd; + nc->ctl_chan = c->self; /* link session -> control channel */ + c->remote_id = nc->self; /* link control -> session channel */ + if (cctx->want_tty && escape_char != 0xffffffff) { - channel_register_filter(c->self, + channel_register_filter(nc->self, client_simple_escape_filter, NULL, client_filter_cleanup, client_new_escape_filter_ctx((int)escape_char)); } - debug3("%s: channel_new: %d", __func__, c->self); - - channel_send_open(c->self); - channel_register_open_confirm(c->self, mux_session_confirm, cctx); - return 0; -} + debug2("%s: channel_new: %d linked to control channel %d", + __func__, nc->self, nc->ctl_chan); -/* ** Multiplexing client support */ + channel_send_open(nc->self); + channel_register_open_confirm(nc->self, mux_session_confirm, cctx); + channel_register_cleanup(nc->self, mux_master_session_cleanup_cb, 0); + + /* prepare reply */ + /* XXX defer until mux_session_confirm() fires */ + buffer_put_int(r, MUX_S_SESSION_OPENED); + buffer_put_int(r, rid); + buffer_put_int(r, nc->self); -/* Exit signal handler */ -static void -control_client_sighandler(int signo) -{ - muxclient_terminate = signo; + return 0; } -/* - * Relay signal handler - used to pass some signals from mux client to - * mux master. - */ -static void -control_client_sigrelay(int signo) +static int +process_mux_alive_check(u_int rid, Channel *c, Buffer *m, Buffer *r) { - int save_errno = errno; + debug2("%s: channel %d: alive check", __func__, c->self); - if (muxserver_pid > 1) - kill(muxserver_pid, signo); + /* prepare reply */ + buffer_put_int(r, MUX_S_ALIVE); + buffer_put_int(r, rid); + buffer_put_int(r, (u_int)getpid()); - errno = save_errno; + return 0; } -/* Check mux client environment variables before passing them to mux master. */ static int -env_permitted(char *env) +process_mux_terminate(u_int rid, Channel *c, Buffer *m, Buffer *r) { - int i, ret; - char name[1024], *cp; - - if ((cp = strchr(env, '=')) == NULL || cp == env) - return (0); - ret = snprintf(name, sizeof(name), "%.*s", (int)(cp - env), env); - if (ret <= 0 || (size_t)ret >= sizeof(name)) - fatal("env_permitted: name '%.100s...' too long", env); + debug2("%s: channel %d: terminate request", __func__, c->self); - for (i = 0; i < options.num_send_env; i++) - if (match_pattern(name, options.send_env[i])) - return (1); + if (options.control_master == SSHCTL_MASTER_ASK || + options.control_master == SSHCTL_MASTER_AUTO_ASK) { + if (!ask_permission("Terminate shared connection to %s? ", + host)) { + debug2("%s: termination refused by user", __func__); + buffer_put_int(r, MUX_S_PERMISSION_DENIED); + buffer_put_int(r, rid); + buffer_put_cstring(r, "Permission denied"); + return 0; + } + } - return (0); + quit_pending = 1; + buffer_put_int(r, MUX_S_OK); + buffer_put_int(r, rid); + /* XXX exit happens too soon - message never makes it to client */ + return 0; } -/* Multiplex client main loop. */ -void -muxclient(const char *path) +static char * +format_forward(u_int ftype, Forward *fwd) { - struct sockaddr_un addr; - int i, r, fd, sock, exitval[2], num_env; - Buffer m; - char *term; - extern char **environ; - u_int allowed, flags; - - if (muxclient_command == 0) - muxclient_command = SSHMUX_COMMAND_OPEN; + char *ret; - switch (options.control_master) { - case SSHCTL_MASTER_AUTO: - case SSHCTL_MASTER_AUTO_ASK: - debug("auto-mux: Trying existing master"); - /* FALLTHROUGH */ - case SSHCTL_MASTER_NO: + switch (ftype) { + case MUX_FWD_LOCAL: + xasprintf(&ret, "local forward %.200s:%d -> %.200s:%d", + (fwd->listen_host == NULL) ? + (options.gateway_ports ? "*" : "LOCALHOST") : + fwd->listen_host, fwd->listen_port, + fwd->connect_host, fwd->connect_port); + break; + case MUX_FWD_DYNAMIC: + xasprintf(&ret, "dynamic forward %.200s:%d -> *", + (fwd->listen_host == NULL) ? + (options.gateway_ports ? "*" : "LOCALHOST") : + fwd->listen_host, fwd->listen_port); + break; + case MUX_FWD_REMOTE: + xasprintf(&ret, "remote forward %.200s:%d -> %.200s:%d", + (fwd->listen_host == NULL) ? + "LOCALHOST" : fwd->listen_host, + fwd->listen_port, + fwd->connect_host, fwd->connect_port); break; default: - return; + fatal("%s: unknown forward type %u", __func__, ftype); } + return ret; +} - memset(&addr, '\0', sizeof(addr)); - addr.sun_family = AF_UNIX; - addr.sun_len = offsetof(struct sockaddr_un, sun_path) + - strlen(path) + 1; +static int +compare_host(const char *a, const char *b) +{ + if (a == NULL && b == NULL) + return 1; + if (a == NULL || b == NULL) + return 0; + return strcmp(a, b) == 0; +} - if (strlcpy(addr.sun_path, path, - sizeof(addr.sun_path)) >= sizeof(addr.sun_path)) - fatal("ControlPath too long"); +static int +compare_forward(Forward *a, Forward *b) +{ + if (!compare_host(a->listen_host, b->listen_host)) + return 0; + if (a->listen_port != b->listen_port) + return 0; + if (!compare_host(a->connect_host, b->connect_host)) + return 0; + if (a->connect_port != b->connect_port) + return 0; - if ((sock = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) - fatal("%s socket(): %s", __func__, strerror(errno)); + return 1; +} - if (connect(sock, (struct sockaddr *)&addr, addr.sun_len) == -1) { - if (muxclient_command != SSHMUX_COMMAND_OPEN) { - fatal("Control socket connect(%.100s): %s", path, - strerror(errno)); - } - if (errno == ENOENT) - debug("Control socket \"%.100s\" does not exist", path); - else { - error("Control socket connect(%.100s): %s", path, - strerror(errno)); +static int +process_mux_open_fwd(u_int rid, Channel *c, Buffer *m, Buffer *r) +{ + Forward fwd; + char *fwd_desc = NULL; + u_int ftype; + int i, ret = 0, freefwd = 1; + + fwd.listen_host = fwd.connect_host = NULL; + if (buffer_get_int_ret(&ftype, m) != 0 || + (fwd.listen_host = buffer_get_string_ret(m, NULL)) == NULL || + buffer_get_int_ret(&fwd.listen_port, m) != 0 || + (fwd.connect_host = buffer_get_string_ret(m, NULL)) == NULL || + buffer_get_int_ret(&fwd.connect_port, m) != 0) { + error("%s: malformed message", __func__); + ret = -1; + goto out; + } + + if (*fwd.listen_host == '\0') { + xfree(fwd.listen_host); + fwd.listen_host = NULL; + } + if (*fwd.connect_host == '\0') { + xfree(fwd.connect_host); + fwd.connect_host = NULL; + } + + debug2("%s: channel %d: request %s", __func__, c->self, + (fwd_desc = format_forward(ftype, &fwd))); + + if (ftype != MUX_FWD_LOCAL && ftype != MUX_FWD_REMOTE && + ftype != MUX_FWD_DYNAMIC) { + logit("%s: invalid forwarding type %u", __func__, ftype); + invalid: + xfree(fwd.listen_host); + xfree(fwd.connect_host); + buffer_put_int(r, MUX_S_FAILURE); + buffer_put_int(r, rid); + buffer_put_cstring(r, "Invalid forwarding request"); + return 0; + } + /* XXX support rport0 forwarding with reply of port assigned */ + if (fwd.listen_port == 0 || fwd.listen_port >= 65536) { + logit("%s: invalid listen port %u", __func__, + fwd.listen_port); + goto invalid; + } + if (fwd.connect_port >= 65536 || (ftype != MUX_FWD_DYNAMIC && + ftype != MUX_FWD_REMOTE && fwd.connect_port == 0)) { + logit("%s: invalid connect port %u", __func__, + fwd.connect_port); + goto invalid; + } + if (ftype != MUX_FWD_DYNAMIC && fwd.connect_host == NULL) { + logit("%s: missing connect host", __func__); + goto invalid; + } + + /* Skip forwards that have already been requested */ + switch (ftype) { + case MUX_FWD_LOCAL: + case MUX_FWD_DYNAMIC: + for (i = 0; i < options.num_local_forwards; i++) { + if (compare_forward(&fwd, + options.local_forwards + i)) { + exists: + debug2("%s: found existing forwarding", + __func__); + buffer_put_int(r, MUX_S_OK); + buffer_put_int(r, rid); + goto out; + } } - close(sock); + break; + case MUX_FWD_REMOTE: + for (i = 0; i < options.num_remote_forwards; i++) { + if (compare_forward(&fwd, + options.remote_forwards + i)) + goto exists; + } + break; + } + + if (options.control_master == SSHCTL_MASTER_ASK || + options.control_master == SSHCTL_MASTER_AUTO_ASK) { + if (!ask_permission("Open %s on %s?", fwd_desc, host)) { + debug2("%s: forwarding refused by user", __func__); + buffer_put_int(r, MUX_S_PERMISSION_DENIED); + buffer_put_int(r, rid); + buffer_put_cstring(r, "Permission denied"); + goto out; + } + } + + if (ftype == MUX_FWD_LOCAL || ftype == MUX_FWD_DYNAMIC) { + if (options.num_local_forwards + 1 >= + SSH_MAX_FORWARDS_PER_DIRECTION || + channel_setup_local_fwd_listener(fwd.listen_host, + fwd.listen_port, fwd.connect_host, fwd.connect_port, + options.gateway_ports) < 0) { + fail: + logit("slave-requested %s failed", fwd_desc); + buffer_put_int(r, MUX_S_FAILURE); + buffer_put_int(r, rid); + buffer_put_cstring(r, "Port forwarding failed"); + goto out; + } + add_local_forward(&options, &fwd); + freefwd = 0; + } else { + /* XXX wait for remote to confirm */ + if (options.num_remote_forwards + 1 >= + SSH_MAX_FORWARDS_PER_DIRECTION || + channel_request_remote_forwarding(fwd.listen_host, + fwd.listen_port, fwd.connect_host, fwd.connect_port) < 0) + goto fail; + add_remote_forward(&options, &fwd); + freefwd = 0; + } + buffer_put_int(r, MUX_S_OK); + buffer_put_int(r, rid); + out: + if (fwd_desc != NULL) + xfree(fwd_desc); + if (freefwd) { + if (fwd.listen_host != NULL) + xfree(fwd.listen_host); + if (fwd.connect_host != NULL) + xfree(fwd.connect_host); + } + return ret; +} + +static int +process_mux_close_fwd(u_int rid, Channel *c, Buffer *m, Buffer *r) +{ + Forward fwd; + char *fwd_desc = NULL; + u_int ftype; + int ret = 0; + + fwd.listen_host = fwd.connect_host = NULL; + if (buffer_get_int_ret(&ftype, m) != 0 || + (fwd.listen_host = buffer_get_string_ret(m, NULL)) == NULL || + buffer_get_int_ret(&fwd.listen_port, m) != 0 || + (fwd.connect_host = buffer_get_string_ret(m, NULL)) == NULL || + buffer_get_int_ret(&fwd.connect_port, m) != 0) { + error("%s: malformed message", __func__); + ret = -1; + goto out; + } + + if (*fwd.listen_host == '\0') { + xfree(fwd.listen_host); + fwd.listen_host = NULL; + } + if (*fwd.connect_host == '\0') { + xfree(fwd.connect_host); + fwd.connect_host = NULL; + } + + debug2("%s: channel %d: request %s", __func__, c->self, + (fwd_desc = format_forward(ftype, &fwd))); + + /* XXX implement this */ + buffer_put_int(r, MUX_S_FAILURE); + buffer_put_int(r, rid); + buffer_put_cstring(r, "unimplemented"); + + out: + if (fwd_desc != NULL) + xfree(fwd_desc); + if (fwd.listen_host != NULL) + xfree(fwd.listen_host); + if (fwd.connect_host != NULL) + xfree(fwd.connect_host); + + return ret; +} + +static int +process_mux_stdio_fwd(u_int rid, Channel *c, Buffer *m, Buffer *r) +{ + Channel *nc; + char *chost; + u_int cport, i, j; + int new_fd[2]; + + if ((chost = buffer_get_string_ret(m, NULL)) == NULL || + buffer_get_int_ret(&cport, m) != 0) { + if (chost != NULL) + xfree(chost); + error("%s: malformed message", __func__); + return -1; + } + + debug2("%s: channel %d: request stdio fwd to %s:%u", + __func__, c->self, chost, cport); + + /* Gather fds from client */ + for(i = 0; i < 2; i++) { + 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++) + close(new_fd[j]); + xfree(chost); + + /* prepare reply */ + buffer_put_int(r, MUX_S_FAILURE); + buffer_put_int(r, rid); + buffer_put_cstring(r, + "did not receive file descriptors"); + return -1; + } + } + + debug3("%s: got fds stdin %d, stdout %d", __func__, + new_fd[0], new_fd[1]); + + /* XXX support multiple child sessions in future */ + if (c->remote_id != -1) { + debug2("%s: session already open", __func__); + /* prepare reply */ + buffer_put_int(r, MUX_S_FAILURE); + buffer_put_int(r, rid); + buffer_put_cstring(r, "Multiple sessions not supported"); + cleanup: + close(new_fd[0]); + close(new_fd[1]); + xfree(chost); + return 0; + } + + if (options.control_master == SSHCTL_MASTER_ASK || + options.control_master == SSHCTL_MASTER_AUTO_ASK) { + if (!ask_permission("Allow forward to to %s:%u? ", + chost, cport)) { + debug2("%s: stdio fwd refused by user", __func__); + /* prepare reply */ + buffer_put_int(r, MUX_S_PERMISSION_DENIED); + buffer_put_int(r, rid); + buffer_put_cstring(r, "Permission denied"); + goto cleanup; + } + } + + /* enable nonblocking unless tty */ + if (!isatty(new_fd[0])) + set_nonblock(new_fd[0]); + if (!isatty(new_fd[1])) + set_nonblock(new_fd[1]); + + nc = channel_connect_stdio_fwd(chost, cport, new_fd[0], new_fd[1]); + + nc->ctl_chan = c->self; /* link session -> control channel */ + c->remote_id = nc->self; /* link control -> session channel */ + + debug2("%s: channel_new: %d linked to control channel %d", + __func__, nc->self, nc->ctl_chan); + + channel_register_cleanup(nc->self, mux_master_session_cleanup_cb, 0); + + /* prepare reply */ + /* XXX defer until channel confirmed */ + buffer_put_int(r, MUX_S_SESSION_OPENED); + buffer_put_int(r, rid); + buffer_put_int(r, nc->self); + + return 0; +} + +/* Channel callbacks fired on read/write from mux slave fd */ +static int +mux_master_read_cb(Channel *c) +{ + struct mux_master_state *state = (struct mux_master_state *)c->mux_ctx; + Buffer in, out; + void *ptr; + u_int type, rid, have, i; + int ret = -1; + + /* Setup ctx and */ + if (c->mux_ctx == NULL) { + state = xcalloc(1, sizeof(state)); + c->mux_ctx = state; + channel_register_cleanup(c->self, + mux_master_control_cleanup_cb, 0); + + /* Send hello */ + buffer_init(&out); + buffer_put_int(&out, MUX_MSG_HELLO); + buffer_put_int(&out, SSHMUX_VER); + /* no extensions */ + buffer_put_string(&c->output, buffer_ptr(&out), + buffer_len(&out)); + buffer_free(&out); + debug3("%s: channel %d: hello sent", __func__, c->self); + return 0; + } + + buffer_init(&in); + buffer_init(&out); + + /* Channel code ensures that we receive whole packets */ + if ((ptr = buffer_get_string_ptr_ret(&c->input, &have)) == NULL) { + malf: + error("%s: malformed message", __func__); + goto out; + } + buffer_append(&in, ptr, have); + + if (buffer_get_int_ret(&type, &in) != 0) + goto malf; + debug3("%s: channel %d packet type 0x%08x len %u", + __func__, c->self, type, buffer_len(&in)); + + if (type == MUX_MSG_HELLO) + rid = 0; + else { + if (!state->hello_rcvd) { + error("%s: expected MUX_MSG_HELLO(0x%08x), " + "received 0x%08x", __func__, MUX_MSG_HELLO, type); + goto out; + } + if (buffer_get_int_ret(&rid, &in) != 0) + goto malf; + } + + for (i = 0; mux_master_handlers[i].handler != NULL; i++) { + if (type == mux_master_handlers[i].type) { + ret = mux_master_handlers[i].handler(rid, c, &in, &out); + break; + } + } + if (mux_master_handlers[i].handler == NULL) { + error("%s: unsupported mux message 0x%08x", __func__, type); + buffer_put_int(&out, MUX_S_FAILURE); + buffer_put_int(&out, rid); + buffer_put_cstring(&out, "unsupported request"); + ret = 0; + } + /* Enqueue reply packet */ + if (buffer_len(&out) != 0) { + buffer_put_string(&c->output, buffer_ptr(&out), + buffer_len(&out)); + } + out: + buffer_free(&in); + buffer_free(&out); + return ret; +} + +void +mux_exit_message(Channel *c, int exitval) +{ + Buffer m; + Channel *mux_chan; + + debug3("%s: channel %d: exit message, evitval %d", __func__, c->self, + exitval); + + if ((mux_chan = channel_by_id(c->ctl_chan)) == NULL) + fatal("%s: channel %d missing mux channel %d", + __func__, c->self, c->ctl_chan); + + /* Append exit message packet to control socket output queue */ + buffer_init(&m); + buffer_put_int(&m, MUX_S_EXIT_MESSAGE); + buffer_put_int(&m, c->self); + buffer_put_int(&m, exitval); + + buffer_put_string(&mux_chan->output, buffer_ptr(&m), buffer_len(&m)); + buffer_free(&m); +} + +/* Prepare a mux master to listen on a Unix domain socket. */ +void +muxserver_listen(void) +{ + struct sockaddr_un addr; + mode_t old_umask; + + 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.sun_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"); + + if ((muxserver_sock = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) + fatal("%s socket(): %s", __func__, strerror(errno)); + + old_umask = umask(0177); + if (bind(muxserver_sock, (struct sockaddr *)&addr, addr.sun_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)); } + umask(old_umask); - if (stdin_null_flag) { - if ((fd = open(_PATH_DEVNULL, O_RDONLY)) == -1) - fatal("open(/dev/null): %s", strerror(errno)); - if (dup2(fd, STDIN_FILENO) == -1) - fatal("dup2: %s", strerror(errno)); - if (fd > STDERR_FILENO) - close(fd); + if (listen(muxserver_sock, 64) == -1) + fatal("%s listen(): %s", __func__, strerror(errno)); + + set_nonblock(muxserver_sock); + + mux_listener_channel = channel_new("mux listener", + SSH_CHANNEL_MUX_LISTENER, muxserver_sock, muxserver_sock, -1, + CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, + 0, addr.sun_path, 1); + mux_listener_channel->mux_rcb = mux_master_read_cb; + debug3("%s: mux listener channel %d fd %d", __func__, + mux_listener_channel->self, mux_listener_channel->sock); +} + +/* Callback on open confirmation in mux master for a mux client session. */ +static void +mux_session_confirm(int id, void *arg) +{ + 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_by_id(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 */ } - term = getenv("TERM"); + if (cctx->want_agent_fwd && options.forward_agent) { + debug("Requesting authentication agent forwarding."); + channel_request_start(id, "auth-agent-req at openssh.com", 0); + packet_send(); + } - flags = 0; - if (tty_flag) - flags |= SSHMUX_FLAG_TTY; - if (subsystem_flag) - flags |= SSHMUX_FLAG_SUBSYS; - if (options.forward_x11) - flags |= SSHMUX_FLAG_X11_FWD; - if (options.forward_agent) - flags |= SSHMUX_FLAG_AGENT_FWD; + client_session2_setup(id, cctx->want_tty, cctx->want_subsys, + cctx->term, &cctx->tio, c->rfd, &cctx->cmd, cctx->env); - signal(SIGPIPE, SIG_IGN); + 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); + } + xfree(cctx); +} + +/* ** Multiplexing client support */ + +/* Exit signal handler */ +static void +control_client_sighandler(int signo) +{ + muxclient_terminate = signo; +} + +/* + * Relay signal handler - used to pass some signals from mux client to + * mux master. + */ +static void +control_client_sigrelay(int signo) +{ + int save_errno = errno; + + if (muxserver_pid > 1) + kill(muxserver_pid, signo); + + errno = save_errno; +} + +static int +mux_client_read(int fd, Buffer *b, u_int need) +{ + u_int have; + ssize_t len; + u_char *p; + struct pollfd pfd; + + pfd.fd = fd; + pfd.events = POLLIN; + p = buffer_append_space(b, need); + for (have = 0; have < need; ) { + if (muxclient_terminate) { + errno = EINTR; + return -1; + } + len = read(fd, p + have, need - have); + if (len < 0) { + switch (errno) { + case EAGAIN: + (void)poll(&pfd, 1, -1); + /* FALLTHROUGH */ + case EINTR: + continue; + default: + return -1; + } + } + if (len == 0) { + errno = EPIPE; + return -1; + } + have += (u_int)len; + } + return 0; +} + +static int +mux_client_write_packet(int fd, Buffer *m) +{ + Buffer queue; + u_int have, need; + int oerrno, len; + u_char *ptr; + struct pollfd pfd; + + pfd.fd = fd; + pfd.events = POLLOUT; + buffer_init(&queue); + buffer_put_string(&queue, buffer_ptr(m), buffer_len(m)); + + need = buffer_len(&queue); + ptr = buffer_ptr(&queue); + + for (have = 0; have < need; ) { + if (muxclient_terminate) { + buffer_free(&queue); + errno = EINTR; + return -1; + } + len = write(fd, ptr + have, need - have); + if (len < 0) { + switch (errno) { + case EAGAIN: + (void)poll(&pfd, 1, -1); + /* FALLTHROUGH */ + case EINTR: + continue; + default: + oerrno = errno; + buffer_free(&queue); + errno = oerrno; + return -1; + } + } + if (len == 0) { + buffer_free(&queue); + errno = EPIPE; + return -1; + } + have += (u_int)len; + } + buffer_free(&queue); + return 0; +} + +static int +mux_client_read_packet(int fd, Buffer *m) +{ + Buffer queue; + u_int need, have; + void *ptr; + int oerrno; + + buffer_init(&queue); + if (mux_client_read(fd, &queue, 4) != 0) { + if ((oerrno = errno) == EPIPE) + debug3("%s: read header failed: %s", __func__, strerror(errno)); + errno = oerrno; + return -1; + } + need = get_u32(buffer_ptr(&queue)); + if (mux_client_read(fd, &queue, need) != 0) { + oerrno = errno; + debug3("%s: read body failed: %s", __func__, strerror(errno)); + errno = oerrno; + return -1; + } + ptr = buffer_get_string_ptr(&queue, &have); + buffer_append(m, ptr, have); + buffer_free(&queue); + return 0; +} + +static int +mux_client_hello_exchange(int fd) +{ + Buffer m; + u_int type, ver; buffer_init(&m); + buffer_put_int(&m, MUX_MSG_HELLO); + buffer_put_int(&m, SSHMUX_VER); + /* no extensions */ - /* Send our command to server */ - buffer_put_int(&m, muxclient_command); - buffer_put_int(&m, flags); - if (ssh_msg_send(sock, SSHMUX_VER, &m) == -1) { - error("%s: msg_send", __func__); - muxerr: - close(sock); + if (mux_client_write_packet(fd, &m) != 0) + fatal("%s: write packet: %s", __func__, strerror(errno)); + + buffer_clear(&m); + + /* Read their HELLO */ + if (mux_client_read_packet(fd, &m) != 0) { buffer_free(&m); - if (muxclient_command != SSHMUX_COMMAND_OPEN) - cleanup_exit(255); - logit("Falling back to non-multiplexed connection"); - xfree(options.control_path); - options.control_path = NULL; - options.control_master = SSHCTL_MASTER_NO; - return; + return -1; + } + + type = buffer_get_int(&m); + if (type != MUX_MSG_HELLO) + fatal("%s: expected HELLO (%u) received %u", + __func__, MUX_MSG_HELLO, type); + ver = buffer_get_int(&m); + if (ver != SSHMUX_VER) + fatal("Unsupported multiplexing protocol version %d " + "(expected %d)", ver, SSHMUX_VER); + debug2("%s: master version %u", __func__, ver); + /* No extensions are presently defined */ + while (buffer_len(&m) > 0) { + char *name = buffer_get_string(&m, NULL); + char *value = buffer_get_string(&m, NULL); + + debug2("Unrecognised master extension \"%s\"", name); + xfree(name); + xfree(value); } + buffer_free(&m); + return 0; +} + +static u_int +mux_client_request_alive(int fd) +{ + Buffer m; + char *e; + u_int pid, type, rid; + + debug3("%s: entering", __func__); + + buffer_init(&m); + buffer_put_int(&m, MUX_C_ALIVE_CHECK); + buffer_put_int(&m, muxclient_request_id); + + if (mux_client_write_packet(fd, &m) != 0) + fatal("%s: write packet: %s", __func__, strerror(errno)); + buffer_clear(&m); - /* Get authorisation status and PID of controlee */ - if (ssh_msg_recv(sock, &m) == -1) { - error("%s: Did not receive reply from master", __func__); - goto muxerr; - } - if (buffer_get_char(&m) != SSHMUX_VER) { - error("%s: Master replied with wrong version", __func__); - goto muxerr; - } - if (buffer_get_int_ret(&allowed, &m) != 0) { - error("%s: bad server reply", __func__); - goto muxerr; - } - if (allowed != 1) { - error("Connection to master denied"); - goto muxerr; + /* Read their reply */ + if (mux_client_read_packet(fd, &m) != 0) { + buffer_free(&m); + return 0; + } + + type = buffer_get_int(&m); + if (type != MUX_S_ALIVE) { + e = buffer_get_string(&m, NULL); + fatal("%s: master returned error: %s", __func__, e); } - muxserver_pid = buffer_get_int(&m); + + if ((rid = buffer_get_int(&m)) != muxclient_request_id) + fatal("%s: out of sequence reply: my id %u theirs %u", + __func__, muxclient_request_id, rid); + pid = buffer_get_int(&m); + buffer_free(&m); + + debug3("%s: done pid = %u", __func__, pid); + + muxclient_request_id++; + + return pid; +} + +static void +mux_client_request_terminate(int fd) +{ + Buffer m; + char *e; + u_int type, rid; + + debug3("%s: entering", __func__); + + buffer_init(&m); + buffer_put_int(&m, MUX_C_TERMINATE); + buffer_put_int(&m, muxclient_request_id); + + if (mux_client_write_packet(fd, &m) != 0) + fatal("%s: write packet: %s", __func__, strerror(errno)); buffer_clear(&m); - switch (muxclient_command) { - case SSHMUX_COMMAND_ALIVE_CHECK: - fprintf(stderr, "Master running (pid=%d)\r\n", - muxserver_pid); - exit(0); - case SSHMUX_COMMAND_TERMINATE: - fprintf(stderr, "Exit request sent.\r\n"); - exit(0); - case SSHMUX_COMMAND_OPEN: - buffer_put_cstring(&m, term ? term : ""); - if (options.escape_char == SSH_ESCAPECHAR_NONE) - buffer_put_int(&m, 0xffffffff); - else - buffer_put_int(&m, options.escape_char); - buffer_append(&command, "\0", 1); - buffer_put_cstring(&m, buffer_ptr(&command)); - - if (options.num_send_env == 0 || environ == NULL) { - buffer_put_int(&m, 0); - } else { - /* Pass environment */ - num_env = 0; - for (i = 0; environ[i] != NULL; i++) { - if (env_permitted(environ[i])) - num_env++; /* Count */ - } - buffer_put_int(&m, num_env); - for (i = 0; environ[i] != NULL && num_env >= 0; i++) { - if (env_permitted(environ[i])) { - num_env--; - buffer_put_cstring(&m, environ[i]); - } - } + /* Read their reply */ + if (mux_client_read_packet(fd, &m) != 0) { + /* Remote end exited already */ + if (errno == EPIPE) { + buffer_free(&m); + return; } + fatal("%s: read from master failed: %s", + __func__, strerror(errno)); + } + + type = buffer_get_int(&m); + if ((rid = buffer_get_int(&m)) != muxclient_request_id) + fatal("%s: out of sequence reply: my id %u theirs %u", + __func__, muxclient_request_id, rid); + switch (type) { + case MUX_S_OK: break; + case MUX_S_PERMISSION_DENIED: + e = buffer_get_string(&m, NULL); + fatal("Master refused termination request: %s", e); + case MUX_S_FAILURE: + e = buffer_get_string(&m, NULL); + fatal("%s: termination request failed: %s", __func__, e); default: - fatal("unrecognised muxclient_command %d", muxclient_command); + fatal("%s: unexpected response from master 0x%08x", + __func__, type); + } + buffer_free(&m); + muxclient_request_id++; +} + +static int +mux_client_request_forward(int fd, u_int ftype, Forward *fwd) +{ + Buffer m; + char *e, *fwd_desc; + u_int type, rid; + + fwd_desc = format_forward(ftype, fwd); + debug("Requesting %s", fwd_desc); + xfree(fwd_desc); + + buffer_init(&m); + buffer_put_int(&m, MUX_C_OPEN_FWD); + buffer_put_int(&m, muxclient_request_id); + buffer_put_int(&m, ftype); + buffer_put_cstring(&m, + fwd->listen_host == NULL ? "" : fwd->listen_host); + buffer_put_int(&m, fwd->listen_port); + buffer_put_cstring(&m, + fwd->connect_host == NULL ? "" : fwd->connect_host); + buffer_put_int(&m, fwd->connect_port); + + if (mux_client_write_packet(fd, &m) != 0) + fatal("%s: write packet: %s", __func__, strerror(errno)); + + buffer_clear(&m); + + /* Read their reply */ + if (mux_client_read_packet(fd, &m) != 0) { + buffer_free(&m); + return -1; } - if (ssh_msg_send(sock, SSHMUX_VER, &m) == -1) { - error("%s: msg_send", __func__); - goto muxerr; + type = buffer_get_int(&m); + if ((rid = buffer_get_int(&m)) != muxclient_request_id) + fatal("%s: out of sequence reply: my id %u theirs %u", + __func__, muxclient_request_id, rid); + switch (type) { + case MUX_S_OK: + break; + case MUX_S_PERMISSION_DENIED: + e = buffer_get_string(&m, NULL); + buffer_free(&m); + error("Master refused forwarding request: %s", e); + return -1; + case MUX_S_FAILURE: + e = buffer_get_string(&m, NULL); + buffer_free(&m); + error("%s: session request failed: %s", __func__, e); + return -1; + default: + fatal("%s: unexpected response from master 0x%08x", + __func__, type); } + buffer_free(&m); + + muxclient_request_id++; + return 0; +} + +static int +mux_client_request_forwards(int fd) +{ + int i; - if (mm_send_fd(sock, STDIN_FILENO) == -1 || - mm_send_fd(sock, STDOUT_FILENO) == -1 || - mm_send_fd(sock, STDERR_FILENO) == -1) { - error("%s: send fds failed", __func__); - goto muxerr; + debug3("%s: requesting forwardings: %d local, %d remote", __func__, + options.num_local_forwards, options.num_remote_forwards); + + /* XXX ExitOnForwardingFailure */ + for (i = 0; i < options.num_local_forwards; i++) { + if (mux_client_request_forward(fd, + options.local_forwards[i].connect_port == 0 ? + MUX_FWD_DYNAMIC : MUX_FWD_LOCAL, + options.local_forwards + i) != 0) + return -1; + } + for (i = 0; i < options.num_remote_forwards; i++) { + if (mux_client_request_forward(fd, MUX_FWD_REMOTE, + options.remote_forwards + i) != 0) + return -1; } + return 0; +} - /* - * Mux errors are non-recoverable from this point as the master - * has ownership of the session now. - */ +static int +mux_client_request_session(int fd) +{ + Buffer m; + char *e, *term; + u_int i, rid, sid, esid, exitval, type, exitval_seen; + extern char **environ; + int devnull; - /* Wait for reply, so master has a chance to gather ttymodes */ + debug3("%s: entering", __func__); + + if ((muxserver_pid = mux_client_request_alive(fd)) == 0) { + error("%s: master alive request failed", __func__); + return -1; + } + + signal(SIGPIPE, SIG_IGN); + + if (stdin_null_flag) { + if ((devnull = open(_PATH_DEVNULL, O_RDONLY)) == -1) + fatal("open(/dev/null): %s", strerror(errno)); + if (dup2(devnull, STDIN_FILENO) == -1) + fatal("dup2: %s", strerror(errno)); + if (devnull > STDERR_FILENO) + close(devnull); + } + + term = getenv("TERM"); + + buffer_init(&m); + buffer_put_int(&m, MUX_C_NEW_SESSION); + buffer_put_int(&m, muxclient_request_id); + buffer_put_int(&m, tty_flag); + buffer_put_int(&m, options.forward_x11); + buffer_put_int(&m, options.forward_agent); + buffer_put_int(&m, subsystem_flag); + buffer_put_int(&m, options.escape_char == SSH_ESCAPECHAR_NONE ? + 0xffffffff : (u_int)options.escape_char); + buffer_put_cstring(&m, term == NULL ? "" : term); + buffer_put_string(&m, buffer_ptr(&command), buffer_len(&command)); + + if (options.num_send_env > 0 && environ != NULL) { + /* Pass environment */ + for (i = 0; environ[i] != NULL; i++) { + if (env_permitted(environ[i])) { + buffer_put_cstring(&m, environ[i]); + } + } + } + + if (mux_client_write_packet(fd, &m) != 0) + fatal("%s: write packet: %s", __func__, strerror(errno)); + + /* Send the stdio file descriptors */ + if (mm_send_fd(fd, STDIN_FILENO) == -1 || + mm_send_fd(fd, STDOUT_FILENO) == -1 || + mm_send_fd(fd, STDERR_FILENO) == -1) + fatal("%s: send fds failed", __func__); + + debug3("%s: session request sent", __func__); + + /* Read their reply */ buffer_clear(&m); - if (ssh_msg_recv(sock, &m) == -1) - fatal("%s: msg_recv", __func__); - if (buffer_get_char(&m) != SSHMUX_VER) - fatal("%s: wrong version", __func__); - buffer_free(&m); + if (mux_client_read_packet(fd, &m) != 0) { + error("%s: read from master failed: %s", + __func__, strerror(errno)); + buffer_free(&m); + return -1; + } + + type = buffer_get_int(&m); + if ((rid = buffer_get_int(&m)) != muxclient_request_id) + fatal("%s: out of sequence reply: my id %u theirs %u", + __func__, muxclient_request_id, rid); + switch (type) { + case MUX_S_SESSION_OPENED: + sid = buffer_get_int(&m); + debug("%s: master session id: %u", __func__, sid); + break; + case MUX_S_PERMISSION_DENIED: + e = buffer_get_string(&m, NULL); + buffer_free(&m); + error("Master refused forwarding request: %s", e); + return -1; + case MUX_S_FAILURE: + e = buffer_get_string(&m, NULL); + buffer_free(&m); + error("%s: forwarding request failed: %s", __func__, e); + return -1; + default: + buffer_free(&m); + error("%s: unexpected response from master 0x%08x", + __func__, type); + return -1; + } + muxclient_request_id++; signal(SIGHUP, control_client_sighandler); signal(SIGINT, control_client_sighandler); @@ -676,42 +1473,229 @@ muxclient(const char *path) /* * Stick around until the controlee closes the client_fd. - * Before it does, it is expected to write this process' exit - * value (one int). This process must read the value and wait for - * the closure of the client_fd; if this one closes early, the - * multiplex master will terminate early too (possibly losing data). + * Before it does, it is expected to write an exit message. + * This process must read the value and wait for the closure of + * the client_fd; if this one closes early, the multiplex master will + * terminate early too (possibly losing data). */ - exitval[0] = 0; - for (i = 0; !muxclient_terminate && i < (int)sizeof(exitval);) { - r = read(sock, (char *)exitval + i, sizeof(exitval) - i); - if (r == 0) { - debug2("Received EOF from master"); + for (exitval = 255, exitval_seen = 0;;) { + buffer_clear(&m); + if (mux_client_read_packet(fd, &m) != 0) break; + type = buffer_get_int(&m); + if (type != MUX_S_EXIT_MESSAGE) { + e = buffer_get_string(&m, NULL); + fatal("%s: master returned error: %s", __func__, e); } - if (r == -1) { - if (errno == EINTR) - continue; - fatal("%s: read %s", __func__, strerror(errno)); - } - i += r; + if ((esid = buffer_get_int(&m)) != sid) + fatal("%s: exit on unknown session: my id %u theirs %u", + __func__, sid, esid); + debug("%s: master session id: %u", __func__, sid); + if (exitval_seen) + fatal("%s: exitval sent twice", __func__); + exitval = buffer_get_int(&m); + exitval_seen = 1; } - close(sock); + close(fd); leave_raw_mode(force_tty_flag); - if (i > (int)sizeof(int)) - fatal("%s: master returned too much data (%d > %lu)", - __func__, i, (u_long)sizeof(int)); + if (muxclient_terminate) { debug2("Exiting on signal %d", muxclient_terminate); - exitval[0] = 255; - } else if (i < (int)sizeof(int)) { + exitval = 255; + } else if (!exitval_seen) { debug2("Control master terminated unexpectedly"); - exitval[0] = 255; + exitval = 255; } else - debug2("Received exit status from master %d", exitval[0]); + debug2("Received exit status from master %d", exitval); if (tty_flag && options.log_level != SYSLOG_LEVEL_QUIET) fprintf(stderr, "Shared connection to %s closed.\r\n", host); - exit(exitval[0]); + exit(exitval); +} + +static int +mux_client_request_stdio_fwd(int fd) +{ + Buffer m; + char *e; + u_int type, rid, sid; + int devnull; + + debug3("%s: entering", __func__); + + if ((muxserver_pid = mux_client_request_alive(fd)) == 0) { + error("%s: master alive request failed", __func__); + return -1; + } + + signal(SIGPIPE, SIG_IGN); + + if (stdin_null_flag) { + if ((devnull = open(_PATH_DEVNULL, O_RDONLY)) == -1) + fatal("open(/dev/null): %s", strerror(errno)); + if (dup2(devnull, STDIN_FILENO) == -1) + fatal("dup2: %s", strerror(errno)); + if (devnull > STDERR_FILENO) + close(devnull); + } + + buffer_init(&m); + buffer_put_int(&m, MUX_C_NEW_STDIO_FWD); + buffer_put_int(&m, muxclient_request_id); + buffer_put_cstring(&m, stdio_forward_host); + buffer_put_int(&m, stdio_forward_port); + + if (mux_client_write_packet(fd, &m) != 0) + fatal("%s: write packet: %s", __func__, strerror(errno)); + + /* Send the stdio file descriptors */ + if (mm_send_fd(fd, STDIN_FILENO) == -1 || + mm_send_fd(fd, STDOUT_FILENO) == -1) + fatal("%s: send fds failed", __func__); + + debug3("%s: stdio forward request sent", __func__); + + /* Read their reply */ + buffer_clear(&m); + + if (mux_client_read_packet(fd, &m) != 0) { + error("%s: read from master failed: %s", + __func__, strerror(errno)); + buffer_free(&m); + return -1; + } + + type = buffer_get_int(&m); + if ((rid = buffer_get_int(&m)) != muxclient_request_id) + fatal("%s: out of sequence reply: my id %u theirs %u", + __func__, muxclient_request_id, rid); + switch (type) { + case MUX_S_SESSION_OPENED: + sid = buffer_get_int(&m); + debug("%s: master session id: %u", __func__, sid); + break; + case MUX_S_PERMISSION_DENIED: + e = buffer_get_string(&m, NULL); + buffer_free(&m); + fatal("Master refused forwarding request: %s", e); + case MUX_S_FAILURE: + e = buffer_get_string(&m, NULL); + buffer_free(&m); + fatal("%s: stdio forwarding request failed: %s", __func__, e); + default: + buffer_free(&m); + error("%s: unexpected response from master 0x%08x", + __func__, type); + return -1; + } + muxclient_request_id++; + + signal(SIGHUP, control_client_sighandler); + signal(SIGINT, control_client_sighandler); + signal(SIGTERM, control_client_sighandler); + signal(SIGWINCH, control_client_sigrelay); + + /* + * Stick around until the controlee closes the client_fd. + */ + buffer_clear(&m); + if (mux_client_read_packet(fd, &m) != 0) { + if (errno == EPIPE || + (errno == EINTR && muxclient_terminate != 0)) + return 0; + fatal("%s: mux_client_read_packet: %s", + __func__, strerror(errno)); + } + fatal("%s: master returned unexpected message %u", __func__, type); +} + +/* Multiplex client main loop. */ +void +muxclient(const char *path) +{ + struct sockaddr_un addr; + int sock; + u_int pid; + + if (muxclient_command == 0) { + if (stdio_forward_host != NULL) + muxclient_command = SSHMUX_COMMAND_STDIO_FWD; + else + muxclient_command = SSHMUX_COMMAND_OPEN; + } + + switch (options.control_master) { + case SSHCTL_MASTER_AUTO: + case SSHCTL_MASTER_AUTO_ASK: + debug("auto-mux: Trying existing master"); + /* FALLTHROUGH */ + case SSHCTL_MASTER_NO: + break; + default: + return; + } + + memset(&addr, '\0', sizeof(addr)); + addr.sun_family = AF_UNIX; + addr.sun_len = offsetof(struct sockaddr_un, sun_path) + + strlen(path) + 1; + + if (strlcpy(addr.sun_path, path, + sizeof(addr.sun_path)) >= sizeof(addr.sun_path)) + fatal("ControlPath too long"); + + if ((sock = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) + fatal("%s socket(): %s", __func__, strerror(errno)); + + if (connect(sock, (struct sockaddr *)&addr, addr.sun_len) == -1) { + switch (muxclient_command) { + case SSHMUX_COMMAND_OPEN: + case SSHMUX_COMMAND_STDIO_FWD: + break; + default: + fatal("Control socket connect(%.100s): %s", path, + strerror(errno)); + } + if (errno == ENOENT) + debug("Control socket \"%.100s\" does not exist", path); + else { + error("Control socket connect(%.100s): %s", path, + strerror(errno)); + } + close(sock); + return; + } + set_nonblock(sock); + + if (mux_client_hello_exchange(sock) != 0) { + error("%s: master hello exchange failed", __func__); + close(sock); + return; + } + + switch (muxclient_command) { + case SSHMUX_COMMAND_ALIVE_CHECK: + if ((pid = mux_client_request_alive(sock)) == 0) + fatal("%s: master alive check failed", __func__); + fprintf(stderr, "Master running (pid=%d)\r\n", pid); + exit(0); + case SSHMUX_COMMAND_TERMINATE: + mux_client_request_terminate(sock); + fprintf(stderr, "Exit request sent.\r\n"); + exit(0); + case SSHMUX_COMMAND_OPEN: + if (mux_client_request_forwards(sock) != 0) { + error("%s: master forward request failed", __func__); + return; + } + mux_client_request_session(sock); + return; + case SSHMUX_COMMAND_STDIO_FWD: + mux_client_request_stdio_fwd(sock); + exit(0); + default: + fatal("unrecognised muxclient_command %d", muxclient_command); + } } Index: nchan.c =================================================================== RCS file: /cvs/src/usr.bin/ssh/nchan.c,v retrieving revision 1.62 diff -u -p -r1.62 nchan.c --- nchan.c 7 Nov 2008 18:50:18 -0000 1.62 +++ nchan.c 24 Jan 2010 10:11:57 -0000 @@ -159,7 +159,7 @@ chan_ibuf_empty(Channel *c) switch (c->istate) { case CHAN_INPUT_WAIT_DRAIN: if (compat20) { - if (!(c->flags & CHAN_CLOSE_SENT)) + if (!(c->flags & (CHAN_CLOSE_SENT|CHAN_LOCAL))) chan_send_eof2(c); chan_set_istate(c, CHAN_INPUT_CLOSED); } else { @@ -276,9 +276,12 @@ static void chan_rcvd_close2(Channel *c) { debug2("channel %d: rcvd close", c->self); - if (c->flags & CHAN_CLOSE_RCVD) - error("channel %d: protocol error: close rcvd twice", c->self); - c->flags |= CHAN_CLOSE_RCVD; + if (!(c->flags & CHAN_LOCAL)) { + if (c->flags & CHAN_CLOSE_RCVD) + error("channel %d: protocol error: close rcvd twice", + c->self); + c->flags |= CHAN_CLOSE_RCVD; + } if (c->type == SSH_CHANNEL_LARVAL) { /* tear down larval channels immediately */ chan_set_ostate(c, CHAN_OUTPUT_CLOSED); @@ -300,11 +303,13 @@ chan_rcvd_close2(Channel *c) chan_set_istate(c, CHAN_INPUT_CLOSED); break; case CHAN_INPUT_WAIT_DRAIN: - chan_send_eof2(c); + if (!(c->flags & CHAN_LOCAL)) + chan_send_eof2(c); chan_set_istate(c, CHAN_INPUT_CLOSED); break; } } + void chan_rcvd_eow(Channel *c) { @@ -452,6 +457,10 @@ chan_is_dead(Channel *c, int do_send) c->self, c->efd, buffer_len(&c->extended)); return 0; } + if (c->flags & CHAN_LOCAL) { + debug2("channel %d: is dead (local)", c->self); + return 1; + } if (!(c->flags & CHAN_CLOSE_SENT)) { if (do_send) { chan_send_close2(c); Index: ssh.c =================================================================== RCS file: /cvs/src/usr.bin/ssh/ssh.c,v retrieving revision 1.331 diff -u -p -r1.331 ssh.c --- ssh.c 11 Jan 2010 01:39:46 -0000 1.331 +++ ssh.c 24 Jan 2010 10:11:57 -0000 @@ -306,6 +306,11 @@ main(int ac, char **av) options.gateway_ports = 1; break; case 'O': + if (stdio_forward_host != NULL) + fatal("Cannot specify multiplexing " + "command with -W"); + else if (muxclient_command != 0) + fatal("Multiplexing command already specified"); if (strcmp(optarg, "check") == 0) muxclient_command = SSHMUX_COMMAND_ALIVE_CHECK; else if (strcmp(optarg, "exit") == 0) @@ -382,6 +387,10 @@ main(int ac, char **av) } break; case 'W': + if (stdio_forward_host != NULL) + fatal("stdio forward already specified"); + if (muxclient_command != 0) + fatal("Cannot specify stdio forward with -O"); if (parse_forward(&fwd, optarg, 1, 0)) { stdio_forward_host = fwd.listen_host; stdio_forward_port = fwd.listen_port; @@ -883,11 +892,18 @@ static int client_setup_stdio_fwd(const char *host_to_connect, u_short port_to_connect) { Channel *c; + int in, out; debug3("client_setup_stdio_fwd %s:%d", host_to_connect, port_to_connect); - if ((c = channel_connect_stdio_fwd(host_to_connect, port_to_connect)) - == NULL) + + in = dup(STDIN_FILENO); + out = dup(STDOUT_FILENO); + if (in < 0 || out < 0) + fatal("channel_connect_stdio_fwd: dup() in/out failed"); + + if ((c = channel_connect_stdio_fwd(host_to_connect, port_to_connect, + in, out)) == NULL) return 0; channel_register_cleanup(c->self, client_cleanup_stdio_fwd, 0); return 1; Index: PROTOCOL.mux =================================================================== RCS file: PROTOCOL.mux diff -N PROTOCOL.mux --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ PROTOCOL.mux 24 Jan 2010 10:11:58 -0000 @@ -0,0 +1,186 @@ +This document describes the multiplexing protocol used by ssh(1)'s +ControlMaster connection-sharing. + +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 + 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. + +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_PERMISSION_DENIED or a +MUX_S_FAILURE. + +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 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. + +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_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$ From sfandino at yahoo.com Sun Jan 24 23:09:48 2010 From: sfandino at yahoo.com (Salvador Fandino) Date: Sun, 24 Jan 2010 04:09:48 -0800 (PST) Subject: ssh(1) multiplexing rewrite In-Reply-To: References: <4B4F490C.70006@yahoo.com> Message-ID: <876823.78499.qm@web52608.mail.re2.yahoo.com> Hi, > I'm hoping to commit the attached version soon. It adds request > identifiers so supporting multiple sessions opened by one control > connection should be possible without an incompatible protocol change > in the future (though the server doesn't support it now). > > I'd appreciate your thoughts on whether the proposed protocol (documented > in PROTOCOL.mux) meets your needs. I believe that will be enough for multiplexing control messages over a single connection. But regarding the send-signal-over-mux feature, and if you want to fix the protocol at this time, a slot should be reserved in MUX_C_MSG_NEW_SESSION and MUX_C_NEW_STDIO_FWD packets for the friendly-names. - Salva PD: excuse me for not having replied to your previous email. I had not forgotten about it, just that I have been quite busy over the las days and I wanted to do it properly. From sxw at inf.ed.ac.uk Sun Jan 24 22:55:05 2010 From: sxw at inf.ed.ac.uk (Simon Wilkinson) Date: Sun, 24 Jan 2010 11:55:05 +0000 Subject: GSSAPI Key Exchange Patch for OpenSSH 5.3p1 Message-ID: From the better-late-than-never-department, I'm pleased to announce the availability of my GSSAPI Key Exchange patches for OpenSSH 5.3p1. This is a pretty minor maintenance release - it contains a couple of fixes to take into account changes to the underlying OpenSSH code, and a compilation fix for when GSSAPI isn't required. Thanks to Colin Wilson and Jim Basney for their bug reports. I'd like to thank the distributors who've been patiently waiting for me to get this done - sorry once again for the delay. Why? ---- Whilst OpenSSH contains support for GSSAPI user authentication, this still relies upon SSH host keys to authenticate the server to the user. For sites with a deployed Kerberos infrastructure this adds an additional, unnecessary, key management burden. GSSAPI key exchange allows the use of security mechanisms such as Kerberos to authenticate the server to the user, removing the need for trusted ssh host keys, and allowing the use of a single security architecture. How? ---- This patch adds support for the RFC4462 GSSAPI key exchange mechanisms to OpenSSH, along with adding some additional, generic, GSSAPI features. It implements: *) gss-group1-sha1-*, gss-group14-sha1-* and gss-gex-sha1-* key exchange mechanisms. (#1242) *) Support for the null host key type (#1242) *) Support for CCAPI credentials caches on Mac OS X (#1245) *) Support for better error handling when an authentication exchange fails due to server misconfiguration (#1244) *) Support for GSSAPI connections to hosts behind a round-robin load balancer (#1008) *) Support for GSSAPI connections to multi-homed hosts, where each interface has a unique name (#928) *) Support for cascading credentials renewal *) Support for the GSSAPIClientIdentity option, to allow the user to select which client identity to use when authenticating to a server. (bugzilla.mindrot.org bug numbers are in brackets) Where? ------ As usual, the code is available from http://www.sxw.org.uk/computing/patches/openssh.html Two patches are available, one containing cascading credentials support, and one without. In addition, the quilt patch series that makes up this release is also provided, for those who wish to pick and choose! Cheers, Simon. From brian at interlinx.bc.ca Mon Jan 25 01:20:06 2010 From: brian at interlinx.bc.ca (Brian J. Murrell) Date: Sun, 24 Jan 2010 14:20:06 +0000 (UTC) Subject: moving X11 portforwarding out into a & quot; plugin& quot; framework References: <20100123040402.20946.qmail@stuge.se> <20100123160432.18350.qmail@stuge.se> <557AB64093762876B19F6035@Ximines.local> <4B5B742C.1030405@zip.com.au> <230347413AB333CFD1770079@nimrod.local> Message-ID: Alex Bligh alex.org.uk> writes: > > That's what I thought. And there is no reason in principle why other > tunneling technologies shouldn't also be vendor extensions, as opposed > to standards Yeah, it seems pretty heavy-weight to have to write a standard for every protocol that might want to tunnel over SSH. Now, what there might be a good case for standardizing is the facility that I am proposing, the one that allows for the specification of tunnels and configuration (on both ends) of, for example, environment variables, the creation of sockets perhaps, etc. > (not that I quite understand what Brian is trying to do). Well, there are a number of examples, off the top of my head, of protocols that could benefit from being tunneled, from a remote machine to a local machine, running a gnome desktop, for example. Pulseaudio is one, dbus is another. There are likely others, perhaps more application specific even. But the idea that every application writer needs to go through a standardization process as well as hacking the openssh code directly just seems, IMHO, silly. Rather, there should be this framework that these application vendors, or O/S distributors perhaps, can utilize to get their protocols forwarded. From alex at alex.org.uk Mon Jan 25 01:31:04 2010 From: alex at alex.org.uk (Alex Bligh) Date: Sun, 24 Jan 2010 14:31:04 +0000 Subject: moving X11 portforwarding out into a & quot; plugin& quot; framework In-Reply-To: References: <20100123040402.20946.qmail@stuge.se> <20100123160432.18350.qmail@stuge.se> <557AB64093762876B19F6035@Ximines.local> <4B5B742C.1030405@zip.com.au> <230347413AB333CFD1770079@nimrod.local> Message-ID: <3726A838B730A9ED270809F8@nimrod.local> --On 24 January 2010 14:20:06 +0000 "Brian J. Murrell" wrote: >> (not that I quite understand what Brian is trying to do). > > Well, there are a number of examples, off the top of my head, of > protocols that could benefit from being tunneled, from a remote machine > to a local machine, running a gnome desktop, for example. Pulseaudio is > one, dbus is another. There are likely others, perhaps more application > specific even. But the idea that every application writer needs to go > through a standardization process as well as hacking the openssh code > directly just seems, IMHO, silly. Rather, there should be this framework > that these application vendors, or O/S distributors perhaps, can utilize > to get their protocols forwarded. Right, but there is generalised forwarding of TCP there already with -L, -R and -D options. x11 is a special case partly because traditionally it uses UDP and partly because it is desirable to integrate authentication. I can see how generalised UDP forwarding might be useful. Equally, if it had more than a trivial userbase, SCTP forwarding. But the whole point of layered stacks is that you should neither need to hack ssh /or/ go through a standardisation process. Just wrap your protocol in TCP or a unix pipe, and you're done. openssh will do it already, without code change. And interoperate with other vendors. That's how unix tends to work. -- Alex Bligh From carson at taltos.org Mon Jan 25 03:01:30 2010 From: carson at taltos.org (Carson Gaspar) Date: Sun, 24 Jan 2010 08:01:30 -0800 Subject: moving X11 portforwarding out into a & quot; plugin& quot; framework In-Reply-To: <3726A838B730A9ED270809F8@nimrod.local> References: <20100123040402.20946.qmail@stuge.se> <20100123160432.18350.qmail@stuge.se> <557AB64093762876B19F6035@Ximines.local> <4B5B742C.1030405@zip.com.au> <230347413AB333CFD1770079@nimrod.local> <3726A838B730A9ED270809F8@nimrod.local> Message-ID: <4B5C6EDA.1040201@taltos.org> On 1/24/10 6:31 AM, Alex Bligh wrote: > -R and -D options. x11 is a special case partly because traditionally > it uses UDP and partly because it is desirable to integrate authentication. Ummm... no. X has been TCP forever. It also supports unix domain sockets on many platforms. -- Carson From djm at mindrot.org Mon Jan 25 07:04:47 2010 From: djm at mindrot.org (Damien Miller) Date: Mon, 25 Jan 2010 07:04:47 +1100 (EST) Subject: ssh(1) multiplexing rewrite In-Reply-To: <876823.78499.qm@web52608.mail.re2.yahoo.com> References: <4B4F490C.70006@yahoo.com> <876823.78499.qm@web52608.mail.re2.yahoo.com> Message-ID: On Sun, 24 Jan 2010, Salvador Fandino wrote: > Hi, > > > > I'm hoping to commit the attached version soon. It adds request > > identifiers so supporting multiple sessions opened by one control > > connection should be possible without an incompatible protocol > > change in the future (though the server doesn't support it now). > > > > I'd appreciate your thoughts on whether the proposed protocol > > (documented in PROTOCOL.mux) meets your needs. > > I believe that will be enough for multiplexing control messages over a > single connection. > > But regarding the send-signal-over-mux feature, and if you want > to fix the protocol at this time, a slot should be reserved in > MUX_C_MSG_NEW_SESSION and MUX_C_NEW_STDIO_FWD packets for the > friendly-names. Thanks, I'll add a reserved field that we can use for this later. -d From brian at interlinx.bc.ca Mon Jan 25 12:04:16 2010 From: brian at interlinx.bc.ca (Brian J. Murrell) Date: Mon, 25 Jan 2010 01:04:16 +0000 (UTC) Subject: moving X11 portforwarding out into a & amp; quot; =?utf-8?b?cGx1Z2luJmFtcDthbXA7CXF1b3Q7CWZyYW1ld29yaw==?= References: <20100123040402.20946.qmail@stuge.se> <20100123160432.18350.qmail@stuge.se> <557AB64093762876B19F6035@Ximines.local> <4B5B742C.1030405@zip.com.au> <230347413AB333CFD1770079@nimrod.local> <3726A838B730A9ED270809F8@nimrod.local> Message-ID: Alex Bligh alex.org.uk> writes: > > Right, but there is generalised forwarding of TCP there already with -L, > -R and -D options. Fair enough. But at least one of the use-cases I am thinking of (dbus) uses a unix socket on the client side. This generalized forwarding should support tunneling into a unix socket, IMHO. > x11 is a special case partly because traditionally > it uses UDP No. X11 is TCP. > and partly because it is desirable to integrate authentication. That might be true but another reason it's desirable to have specialized forwarding and not just generic TCP forwarding is for the setting of the DISPLAY variable on the server side. That is another feature that I think a generalized forwarding framework should/would support. As I've said before, it might also require creating a socket and shuttling data to and from it on the server and/or client side. > Just wrap your protocol > in TCP or a unix pipe, and you're done. I'd bet more times than not it's not that simple. Typically, processes on the server side need to know where to reach it's peer and, in the desktop protocol paradigm at least, that is typically done by setting and reading environment variables. From brian at interlinx.bc.ca Mon Jan 25 23:10:08 2010 From: brian at interlinx.bc.ca (Brian J. Murrell) Date: Mon, 25 Jan 2010 12:10:08 +0000 (UTC) Subject: moving X11 portforwarding out into a " plugin" framework References: Message-ID: Damien Miller mindrot.org> writes: > > You should be able to do most of what you want using a Subsystem (see > sshd_config) and a helper program on the client side. Hrm. Doesn't a Subsystem typically invoke something on the server side other than the typical shell session? But what if I want really is just the usual shell session, prepped with the server end of a number of different forwarding setups? I suppose I could write a Subsystem that simply mimics the existing "start a shell" functionality, but with my proposed forwarding (and the env. vars that go with them) but that seems a little hacky. b. From imorgan at nas.nasa.gov Wed Jan 27 12:35:55 2010 From: imorgan at nas.nasa.gov (Iain Morgan) Date: Tue, 26 Jan 2010 17:35:55 -0800 Subject: Multiplexing bug on client exit Message-ID: <20100127013555.GH1595@linux55.nas.nasa.gov> Hi, With the 20100127 snapshot, there appears to be a bug in the multiplexing support that causes the master to die under some circumstances when a slave session exits. The error messages that I am getting are: cfe1.imorgan> exit Connection to cfe1 closed. $ channel_by_id: 2: bad id: channel free client_input_channel_req: channel 2: unknown channel channel_by_id: 2: bad id: channel free Disconnecting: Received oclose for nonexistent channel 2. This is triggered by using a multiplexed connection to a bastion host and a ProxyCommand (similar to netcat) associated with the slave session to reach the host behind the bastion. However, a slave connection to the same bastion does not exhibit this problem, which makes me suspect that it has to do with the use of a ProxyCommand over the muxed session. The same configuration worked fine with 5.3p1. -- Iain Morgan From djm at mindrot.org Wed Jan 27 14:09:14 2010 From: djm at mindrot.org (Damien Miller) Date: Wed, 27 Jan 2010 14:09:14 +1100 (EST) Subject: Multiplexing bug on client exit In-Reply-To: <20100127013555.GH1595@linux55.nas.nasa.gov> References: <20100127013555.GH1595@linux55.nas.nasa.gov> Message-ID: On Tue, 26 Jan 2010, Iain Morgan wrote: > Hi, > > With the 20100127 snapshot, there appears to be a bug in the > multiplexing support that causes the master to die under some > circumstances when a slave session exits. Could you try to catch this with both the master and slave in debug mode? "ssh -ddd" I'll see if I can find it in the meantime. Thanks, Damien From djm at mindrot.org Wed Jan 27 14:21:25 2010 From: djm at mindrot.org (Damien Miller) Date: Wed, 27 Jan 2010 14:21:25 +1100 (EST) Subject: Multiplexing bug on client exit In-Reply-To: References: <20100127013555.GH1595@linux55.nas.nasa.gov> Message-ID: On Wed, 27 Jan 2010, Damien Miller wrote: > > > On Tue, 26 Jan 2010, Iain Morgan wrote: > > > Hi, > > > > With the 20100127 snapshot, there appears to be a bug in the > > multiplexing support that causes the master to die under some > > circumstances when a slave session exits. > > Could you try to catch this with both the master and slave in debug > mode? "ssh -ddd" > > I'll see if I can find it in the meantime. You could also try this diff, but I'd appreciate seeing a debug trace from before you apply it if possible. Index: nchan.c =================================================================== RCS file: /cvs/src/usr.bin/ssh/nchan.c,v retrieving revision 1.63 diff -u -p -r1.63 nchan.c --- nchan.c 26 Jan 2010 01:28:35 -0000 1.63 +++ nchan.c 27 Jan 2010 03:21:12 -0000 @@ -159,7 +159,7 @@ chan_ibuf_empty(Channel *c) switch (c->istate) { case CHAN_INPUT_WAIT_DRAIN: if (compat20) { - if (!(c->flags & (CHAN_CLOSE_SENT|CHAN_LOCAL))) + if (!(c->flags & CHAN_CLOSE_SENT)) chan_send_eof2(c); chan_set_istate(c, CHAN_INPUT_CLOSED); } else { @@ -240,6 +240,10 @@ chan_send_ieof1(Channel *c) switch (c->istate) { case CHAN_INPUT_OPEN: case CHAN_INPUT_WAIT_DRAIN: + if ((c->flags & CHAN_LOCAL)) { + debug3("channel %d: local, not sending ieof", c->self); + return; + } packet_start(SSH_MSG_CHANNEL_INPUT_EOF); packet_put_int(c->remote_id); packet_send(); @@ -257,6 +261,11 @@ chan_send_oclose1(Channel *c) switch (c->ostate) { case CHAN_OUTPUT_OPEN: case CHAN_OUTPUT_WAIT_DRAIN: + if ((c->flags & CHAN_LOCAL)) { + debug3("channel %d: local, not sending oclose", + c->self); + return; + } buffer_clear(&c->output); packet_start(SSH_MSG_CHANNEL_OUTPUT_CLOSE); packet_put_int(c->remote_id); @@ -303,8 +312,7 @@ chan_rcvd_close2(Channel *c) chan_set_istate(c, CHAN_INPUT_CLOSED); break; case CHAN_INPUT_WAIT_DRAIN: - if (!(c->flags & CHAN_LOCAL)) - chan_send_eof2(c); + chan_send_eof2(c); chan_set_istate(c, CHAN_INPUT_CLOSED); break; } @@ -353,6 +361,10 @@ chan_send_eof2(Channel *c) debug2("channel %d: send eof", c->self); switch (c->istate) { case CHAN_INPUT_WAIT_DRAIN: + if ((c->flags & CHAN_LOCAL)) { + debug3("channel %d: local, not sending close", c->self); + return; + } packet_start(SSH2_MSG_CHANNEL_EOF); packet_put_int(c->remote_id); packet_send(); @@ -372,6 +384,8 @@ chan_send_close2(Channel *c) c->istate != CHAN_INPUT_CLOSED) { error("channel %d: cannot send close for istate/ostate %d/%d", c->self, c->istate, c->ostate); + } else if ((c->flags & CHAN_LOCAL)) { + debug3("channel %d: local, not sending close", c->self); } else if (c->flags & CHAN_CLOSE_SENT) { error("channel %d: already sent close", c->self); } else { @@ -388,6 +402,9 @@ chan_send_eow2(Channel *c) if (c->ostate == CHAN_OUTPUT_CLOSED) { error("channel %d: must not sent eow on closed output", c->self); + return; + } else if ((c->flags & CHAN_LOCAL)) { + debug3("channel %d: local, not sending close", c->self); return; } if (!(datafellows & SSH_NEW_OPENSSH)) From djm at mindrot.org Wed Jan 27 14:57:50 2010 From: djm at mindrot.org (Damien Miller) Date: Wed, 27 Jan 2010 14:57:50 +1100 (EST) Subject: Multiplexing bug on client exit In-Reply-To: References: <20100127013555.GH1595@linux55.nas.nasa.gov> Message-ID: On Wed, 27 Jan 2010, Damien Miller wrote: > > Could you try to catch this with both the master and slave in debug > > mode? "ssh -ddd" > > > > I'll see if I can find it in the meantime. > > You could also try this diff, but I'd appreciate seeing a debug trace from > before you apply it if possible. better diff; this fixes one crash case I could reproduce pretty easily: ssh -nNf host ssh host "sleep 20" self, c->remote_id); c->remote_id = -1; sc->ctl_chan = -1; - chan_mark_dead(sc); + if (sc->type != SSH_CHANNEL_OPEN) { + debug2("%s: channel %d: not open", __func__, sc->self); + chan_mark_dead(c); + } else { + chan_read_failed(sc); + chan_write_failed(sc); + } } channel_cancel_cleanup(c->self); } Index: nchan.c =================================================================== RCS file: /cvs/src/usr.bin/ssh/nchan.c,v retrieving revision 1.63 diff -u -p -r1.63 nchan.c --- nchan.c 26 Jan 2010 01:28:35 -0000 1.63 +++ nchan.c 27 Jan 2010 03:56:25 -0000 @@ -159,7 +159,7 @@ chan_ibuf_empty(Channel *c) switch (c->istate) { case CHAN_INPUT_WAIT_DRAIN: if (compat20) { - if (!(c->flags & (CHAN_CLOSE_SENT|CHAN_LOCAL))) + if (!(c->flags & CHAN_CLOSE_SENT)) chan_send_eof2(c); chan_set_istate(c, CHAN_INPUT_CLOSED); } else { @@ -240,6 +240,10 @@ chan_send_ieof1(Channel *c) switch (c->istate) { case CHAN_INPUT_OPEN: case CHAN_INPUT_WAIT_DRAIN: + if ((c->flags & CHAN_LOCAL)) { + debug3("channel %d: local, not sending ieof", c->self); + return; + } packet_start(SSH_MSG_CHANNEL_INPUT_EOF); packet_put_int(c->remote_id); packet_send(); @@ -257,6 +261,11 @@ chan_send_oclose1(Channel *c) switch (c->ostate) { case CHAN_OUTPUT_OPEN: case CHAN_OUTPUT_WAIT_DRAIN: + if ((c->flags & CHAN_LOCAL)) { + debug3("channel %d: local, not sending oclose", + c->self); + return; + } buffer_clear(&c->output); packet_start(SSH_MSG_CHANNEL_OUTPUT_CLOSE); packet_put_int(c->remote_id); @@ -303,8 +312,7 @@ chan_rcvd_close2(Channel *c) chan_set_istate(c, CHAN_INPUT_CLOSED); break; case CHAN_INPUT_WAIT_DRAIN: - if (!(c->flags & CHAN_LOCAL)) - chan_send_eof2(c); + chan_send_eof2(c); chan_set_istate(c, CHAN_INPUT_CLOSED); break; } @@ -353,6 +361,10 @@ chan_send_eof2(Channel *c) debug2("channel %d: send eof", c->self); switch (c->istate) { case CHAN_INPUT_WAIT_DRAIN: + if ((c->flags & CHAN_LOCAL)) { + debug3("channel %d: local, not sending close", c->self); + return; + } packet_start(SSH2_MSG_CHANNEL_EOF); packet_put_int(c->remote_id); packet_send(); @@ -372,6 +384,8 @@ chan_send_close2(Channel *c) c->istate != CHAN_INPUT_CLOSED) { error("channel %d: cannot send close for istate/ostate %d/%d", c->self, c->istate, c->ostate); + } else if ((c->flags & CHAN_LOCAL)) { + debug3("channel %d: local, not sending close", c->self); } else if (c->flags & CHAN_CLOSE_SENT) { error("channel %d: already sent close", c->self); } else { @@ -388,6 +402,9 @@ chan_send_eow2(Channel *c) if (c->ostate == CHAN_OUTPUT_CLOSED) { error("channel %d: must not sent eow on closed output", c->self); + return; + } else if ((c->flags & CHAN_LOCAL)) { + debug3("channel %d: local, not sending close", c->self); return; } if (!(datafellows & SSH_NEW_OPENSSH)) From ravindra1103 at gmail.com Wed Jan 27 17:51:34 2010 From: ravindra1103 at gmail.com (ravindra Chavalam) Date: Wed, 27 Jan 2010 12:21:34 +0530 Subject: sshd killed due to dos attack Message-ID: Hi, I am not sure to report this as a bug. so mailing to the list. I have sshd(openssh3.5p1) server running on my router and when i run tcpjunk to that port, sshd gets killed after some time 192.168.71.1 is my sshd server and 192.168.71.4 is my client from where i send my dos attack This is the tcpjunk command i gave to the ssh server #tcpjunk -s 192.168.71.1 -p 22 -c req -i 100 req session file contains string below attached is the netstat output. They are lot of these like these but i just pasted two lines for reference #netstat -an|grep ":22" tcp 0 0 192.168.71.1:22 192.168.71.4:37757 TIME_WAIT tcp 0 0 192.168.71.1:22 192.168.71.4:55207 TIME_WAIT ... ... ... ... Can any one on tell me where in the openssh code i have to search to find out the root cause for this issue Thanks a lot in advance From djm at mindrot.org Thu Jan 28 00:01:56 2010 From: djm at mindrot.org (Damien Miller) Date: Thu, 28 Jan 2010 00:01:56 +1100 (EST) Subject: Multiplexing bug on client exit In-Reply-To: References: <20100127013555.GH1595@linux55.nas.nasa.gov> Message-ID: On Wed, 27 Jan 2010, Damien Miller wrote: > On Wed, 27 Jan 2010, Damien Miller wrote: > > > > Could you try to catch this with both the master and slave in debug > > > mode? "ssh -ddd" > > > > > > I'll see if I can find it in the meantime. > > > > You could also try this diff, but I'd appreciate seeing a debug trace from > > before you apply it if possible. > > better diff; this fixes one crash case I could reproduce pretty easily: > > ssh -nNf host > ssh host "sleep 20" ^C > (wait) actually, you should only need this part: Index: mux.c =================================================================== RCS file: /cvs/src/usr.bin/ssh/mux.c,v retrieving revision 1.11 diff -u -p -r1.11 mux.c --- mux.c 26 Jan 2010 02:15:20 -0000 1.11 +++ mux.c 27 Jan 2010 03:56:25 -0000 @@ -193,7 +193,13 @@ mux_master_control_cleanup_cb(int cid, v __func__, c->self, c->remote_id); c->remote_id = -1; sc->ctl_chan = -1; - chan_mark_dead(sc); + if (sc->type != SSH_CHANNEL_OPEN) { + debug2("%s: channel %d: not open", __func__, sc->self); + chan_mark_dead(c); + } else { + chan_read_failed(sc); + chan_write_failed(sc); + } } channel_cancel_cleanup(c->self); } From mouring at eviladmin.org Thu Jan 28 01:40:39 2010 From: mouring at eviladmin.org (Ben Lindstrom) Date: Wed, 27 Jan 2010 08:40:39 -0600 Subject: sshd killed due to dos attack In-Reply-To: References: Message-ID: You really need to explain what you are doing as a DOS attack.. If all you are doing is filling up the max unauthenticated connections this is a known feature and you really should read the sshd_config manpage on "MaxStartups" feature. - Ben On Jan 27, 2010, at 12:51 AM, ravindra Chavalam wrote: > Hi, > > I am not sure to report this as a bug. so mailing to the list. > > > I have sshd(openssh3.5p1) server running on my router and when i run tcpjunk > to that port, sshd gets killed after some time > > 192.168.71.1 is my sshd server and 192.168.71.4 is my client from where i > send my dos attack > > This is the tcpjunk command i gave to the ssh server > > #tcpjunk -s 192.168.71.1 -p 22 -c req -i 100 > req session file contains string > > below attached is the netstat output. They are lot of these like these but i > just pasted two lines for reference > > #netstat -an|grep ":22" > tcp 0 0 192.168.71.1:22 192.168.71.4:37757 TIME_WAIT > tcp 0 0 192.168.71.1:22 192.168.71.4:55207 TIME_WAIT > ... > ... > > ... > > ... > > > Can any one on tell me where in the openssh code i have to search to find > out the root cause for this issue > > > Thanks a lot in advance > _______________________________________________ > openssh-unix-dev mailing list > openssh-unix-dev at mindrot.org > https://lists.mindrot.org/mailman/listinfo/openssh-unix-dev From imorgan at nas.nasa.gov Thu Jan 28 07:04:57 2010 From: imorgan at nas.nasa.gov (Iain Morgan) Date: Wed, 27 Jan 2010 12:04:57 -0800 Subject: Multiplexing bug on client exit In-Reply-To: References: <20100127013555.GH1595@linux55.nas.nasa.gov> Message-ID: <20100127200457.GA18183@linux55.nas.nasa.gov> On Wed, Jan 27, 2010 at 07:01:56 -0600, Damien Miller wrote: > On Wed, 27 Jan 2010, Damien Miller wrote: > > > On Wed, 27 Jan 2010, Damien Miller wrote: > > > > > > Could you try to catch this with both the master and slave in debug > > > > mode? "ssh -ddd" > > > > > > > > I'll see if I can find it in the meantime. > > > > > > You could also try this diff, but I'd appreciate seeing a debug trace from > > > before you apply it if possible. > > > > better diff; this fixes one crash case I could reproduce pretty easily: > > > > ssh -nNf host > > ssh host "sleep 20" > ^C > > (wait) > > actually, you should only need this part: Yes, this patch has fixed the issue for me. Thanks -- Iain > > Index: mux.c > =================================================================== > RCS file: /cvs/src/usr.bin/ssh/mux.c,v > retrieving revision 1.11 > diff -u -p -r1.11 mux.c > --- mux.c 26 Jan 2010 02:15:20 -0000 1.11 > +++ mux.c 27 Jan 2010 03:56:25 -0000 > @@ -193,7 +193,13 @@ mux_master_control_cleanup_cb(int cid, v > __func__, c->self, c->remote_id); > c->remote_id = -1; > sc->ctl_chan = -1; > - chan_mark_dead(sc); > + if (sc->type != SSH_CHANNEL_OPEN) { > + debug2("%s: channel %d: not open", __func__, sc->self); > + chan_mark_dead(c); > + } else { > + chan_read_failed(sc); > + chan_write_failed(sc); > + } > } > channel_cancel_cleanup(c->self); > } -- Iain Morgan From ravindra1103 at gmail.com Thu Jan 28 20:30:11 2010 From: ravindra1103 at gmail.com (ravindra Chavalam) Date: Thu, 28 Jan 2010 15:00:11 +0530 Subject: sshd killed due to dos attack In-Reply-To: References: Message-ID: Hi Ben, Thanks a lot for the response. I gave MaxStartups 10:30:60 (these are defaults i suppose for our requirements). Still facing the same issue. Is sshd getting killed is the expected behaviour?in that case how can i work around so that instead of killing sshd i just drop extra connections. Also interesting fact is drop_connections is not getting called? Thanks & Regards, Ravindranath On Wed, Jan 27, 2010 at 8:10 PM, Ben Lindstrom wrote: > > You really need to explain what you are doing as a DOS attack.. If all you > are doing is filling up the max unauthenticated connections this is a known > feature and you really should read the sshd_config manpage on "MaxStartups" > feature. > > - Ben > > > On Jan 27, 2010, at 12:51 AM, ravindra Chavalam wrote: > > > Hi, > > > > I am not sure to report this as a bug. so mailing to the list. > > > > > > I have sshd(openssh3.5p1) server running on my router and when i run > tcpjunk > > to that port, sshd gets killed after some time > > > > 192.168.71.1 is my sshd server and 192.168.71.4 is my client from where i > > send my dos attack > > > > This is the tcpjunk command i gave to the ssh server > > > > #tcpjunk -s 192.168.71.1 -p 22 -c req -i 100 > > req session file contains string > > > > below attached is the netstat output. They are lot of these like these > but i > > just pasted two lines for reference > > > > #netstat -an|grep ":22" > > tcp 0 0 192.168.71.1:22 192.168.71.4:37757 TIME_WAIT > > tcp 0 0 192.168.71.1:22 192.168.71.4:55207 TIME_WAIT > > ... > > ... > > > > ... > > > > ... > > > > > > Can any one on tell me where in the openssh code i have to search to find > > out the root cause for this issue > > > > > > Thanks a lot in advance > > _______________________________________________ > > openssh-unix-dev mailing list > > openssh-unix-dev at mindrot.org > > https://lists.mindrot.org/mailman/listinfo/openssh-unix-dev > > From ravindra1103 at gmail.com Thu Jan 28 21:12:30 2010 From: ravindra1103 at gmail.com (ravindra Chavalam) Date: Thu, 28 Jan 2010 15:42:30 +0530 Subject: sshd killed due to dos attack In-Reply-To: References: Message-ID: Hi Ben, One more interesting observation. I kept this line before drop_connection(..) function call... syslog (LOG_INFO,"startups:%d,rate:%d,begin:%d,full:%d",startups,options.max_startups_rate,options.max_startups_begin,options.max_startups); if (drop_connection(startups) == 1) { and my log after running tcpjunk shows below log when the sshd got killed. So based on the parameters shown in the log and the way drop_connection is implemented it seems no way this fucnion is called. Note:startups is 0 always and drop_connection always retuns FALSE tail -f /var/log/messages Jan 28 15:30:24 gateway user.info sshd: startups:0,rate:100,begin:5,full:5 Jan 28 15:30:24 gateway user.info sshd: startups:0,rate:100,begin:5,full:5 Jan 28 15:30:24 gateway user.info sshd: startups:0,rate:100,begin:5,full:5 Jan 28 15:30:24 gateway user.info sshd: startups:0,rate:100,begin:5,full:5 Jan 28 15:30:24 gateway user.info sshd: startups:0,rate:100,begin:5,full:5 Jan 28 15:30:24 gateway user.info sshd: startups:0,rate:100,begin:5,full:5 Jan 28 15:30:24 gateway user.info sshd: startups:0,rate:100,begin:5,full:5 Jan 28 15:30:24 gateway user.info sshd: startups:0,rate:100,begin:5,full:5 Jan 28 15:30:24 gateway user.info sshd: startups:0,rate:100,begin:5,full:5 Jan 28 15:30:24 gateway user.info sshd: startups:0,rate:100,begin:5,full:5 Thanks & Regards, Ravindranath On Thu, Jan 28, 2010 at 3:00 PM, ravindra Chavalam wrote: > Hi Ben, > > Thanks a lot for the response. I gave MaxStartups 10:30:60 (these are > defaults i suppose for our requirements). Still facing the same issue. Is > sshd getting killed is the expected behaviour?in that case how can i work > around so that instead of killing sshd i just drop extra connections. Also > interesting fact is drop_connections is not getting called? > > Thanks & Regards, > Ravindranath > > On Wed, Jan 27, 2010 at 8:10 PM, Ben Lindstrom wrote: > >> >> You really need to explain what you are doing as a DOS attack.. If all you >> are doing is filling up the max unauthenticated connections this is a known >> feature and you really should read the sshd_config manpage on "MaxStartups" >> feature. >> >> - Ben >> >> >> On Jan 27, 2010, at 12:51 AM, ravindra Chavalam wrote: >> >> > Hi, >> > >> > I am not sure to report this as a bug. so mailing to the list. >> > >> > >> > I have sshd(openssh3.5p1) server running on my router and when i run >> tcpjunk >> > to that port, sshd gets killed after some time >> > >> > 192.168.71.1 is my sshd server and 192.168.71.4 is my client from where >> i >> > send my dos attack >> > >> > This is the tcpjunk command i gave to the ssh server >> > >> > #tcpjunk -s 192.168.71.1 -p 22 -c req -i 100 >> > req session file contains string >> > >> > below attached is the netstat output. They are lot of these like these >> but i >> > just pasted two lines for reference >> > >> > #netstat -an|grep ":22" >> > tcp 0 0 192.168.71.1:22 192.168.71.4:37757 TIME_WAIT >> > tcp 0 0 192.168.71.1:22 192.168.71.4:55207 TIME_WAIT >> > ... >> > ... >> > >> > ... >> > >> > ... >> > >> > >> > Can any one on tell me where in the openssh code i have to search to >> find >> > out the root cause for this issue >> > >> > >> > Thanks a lot in advance >> > _______________________________________________ >> > openssh-unix-dev mailing list >> > openssh-unix-dev at mindrot.org >> > https://lists.mindrot.org/mailman/listinfo/openssh-unix-dev >> >> > From ravindra1103 at gmail.com Thu Jan 28 22:27:15 2010 From: ravindra1103 at gmail.com (ravindra Chavalam) Date: Thu, 28 Jan 2010 16:57:15 +0530 Subject: sshd killed due to dos attack In-Reply-To: References: Message-ID: Sorry for repetitive mails. But here is my one more observation -->First i made five successfull ssh connections to the sshd from my local host.(It wont allow me to do the 6th one becase 5 are the max limit of ssh connections) -->Then i ran the same tcpjunk command -->Now all the logs are filled with the below logs Jan 28 16:49:17 gateway user.info sshd: dropping connections Jan 28 16:49:17 gateway user.info sshd: dropping connections Jan 28 16:49:17 gateway user.info sshd: dropping connections Jan 28 16:49:17 gateway user.info sshd: dropping connections Jan 28 16:49:17 gateway user.info sshd: dropping connections Jan 28 16:49:17 gateway user.info sshd: dropping connections Jan 28 16:49:17 gateway user.info sshd: dropping connections Jan 28 16:49:17 gateway user.info sshd: dropping connections Jan 28 16:49:17 gateway user.info sshd: dropping connections Jan 28 16:49:17 gateway user.info sshd: dropping connections Jan 28 16:49:17 gateway user.info sshd: dropping connections Jan 28 16:49:17 gateway user.info sshd: dropping connections Jan 28 16:49:17 gateway user.info sshd: dropping connections Jan 28 16:49:17 gateway user.info sshd: dropping connections Jan 28 16:49:17 gateway user.info sshd: dropping connections Jan 28 16:49:17 gateway user.info sshd: dropping connections Jan 28 16:49:17 gateway user.info sshd: dropping connections also netstat-an|grep ":22" shows only tcp 6 0 0.0.0.0:22 0.0.0.0:* LISTEN tcp 0 0 192.168.71.1:22 192.168.71.4:36320 SYN_RECV tcp 0 0 192.168.71.1:22 192.168.71.4:36246 SYN_RECV tcp 0 0 192.168.71.1:22 192.168.71.4:35994 SYN_RECV tcp 0 0 192.168.71.1:22 192.168.71.4:34288 SYN_RECV tcp 0 0 192.168.71.1:22 192.168.71.4:60815 SYN_RECV tcp 0 0 192.168.71.1:22 192.168.71.4:35582 SYN_RECV tcp 0 0 192.168.71.1:22 192.168.71.4:36102 SYN_RECV tcp 0 0 192.168.71.1:22 192.168.71.4:35821 SYN_RECV tcp 0 0 192.168.71.1:22 192.168.71.4:59029 SYN_RECV tcp 0 0 192.168.71.1:22 192.168.71.4:50535 SYN_RECV tcp 0 0 192.168.71.1:22 192.168.71.4:33056 SYN_RECV tcp 0 0 192.168.71.1:22 192.168.71.4:41566 SYN_RECV tcp 0 0 192.168.71.1:22 192.168.71.4:32920 SYN_RECV tcp 101 0 192.168.71.1:22 192.168.71.4:36455 ESTABLISHED tcp 101 0 192.168.71.1:22 192.168.71.4:36450 ESTABLISHED tcp 0 0 192.168.71.1:22 192.168.71.4:38544 ESTABLISHED tcp 0 0 192.168.71.1:22 192.168.71.4:60814 FIN_WAIT2 tcp 101 0 192.168.71.1:22 192.168.71.4:36453 ESTABLISHED tcp 101 0 192.168.71.1:22 192.168.71.4:36452 ESTABLISHED tcp 101 0 192.168.71.1:22 192.168.71.4:36449 ESTABLISHED tcp 101 0 192.168.71.1:22 192.168.71.4:36448 ESTABLISHED tcp 0 0 192.168.71.1:22 192.168.71.4:38546 ESTABLISHED tcp 0 0 192.168.71.1:22 192.168.71.4:33041 FIN_WAIT2 tcp 101 0 192.168.71.1:22 192.168.71.4:36451 ESTABLISHED tcp 0 0 192.168.71.1:22 192.168.71.4:45284 FIN_WAIT2 Note:There are no TIME_WAIT as in previous case where sshd gets killed -->Now when i ran multiple tcpjunk sessions also sshd is working properly without any issues Hope this information gives some clues Thanks & Regards, Ravindranath On Thu, Jan 28, 2010 at 3:42 PM, ravindra Chavalam wrote: > Hi Ben, > One more interesting observation. I kept this line before > drop_connection(..) function call... > syslog > (LOG_INFO,"startups:%d,rate:%d,begin:%d,full:%d",startups,options.max_startups_rate,options.max_startups_begin,options.max_startups); > if (drop_connection(startups) == 1) { > > > and my log after running tcpjunk shows below log when the sshd got killed. > So based on the parameters shown in the log and the way drop_connection is > implemented it seems no way this fucnion is called. Note:startups is 0 > always and drop_connection always retuns FALSE > > tail -f /var/log/messages > Jan 28 15:30:24 gateway user.info sshd: > startups:0,rate:100,begin:5,full:5 > Jan 28 15:30:24 gateway user.info sshd: > startups:0,rate:100,begin:5,full:5 > Jan 28 15:30:24 gateway user.info sshd: > startups:0,rate:100,begin:5,full:5 > Jan 28 15:30:24 gateway user.info sshd: > startups:0,rate:100,begin:5,full:5 > Jan 28 15:30:24 gateway user.info sshd: > startups:0,rate:100,begin:5,full:5 > Jan 28 15:30:24 gateway user.info sshd: > startups:0,rate:100,begin:5,full:5 > Jan 28 15:30:24 gateway user.info sshd: > startups:0,rate:100,begin:5,full:5 > Jan 28 15:30:24 gateway user.info sshd: > startups:0,rate:100,begin:5,full:5 > Jan 28 15:30:24 gateway user.info sshd: > startups:0,rate:100,begin:5,full:5 > Jan 28 15:30:24 gateway user.info sshd: > startups:0,rate:100,begin:5,full:5 > > > Thanks & Regards, > Ravindranath > > > On Thu, Jan 28, 2010 at 3:00 PM, ravindra Chavalam > wrote: > >> Hi Ben, >> >> Thanks a lot for the response. I gave MaxStartups 10:30:60 (these are >> defaults i suppose for our requirements). Still facing the same issue. Is >> sshd getting killed is the expected behaviour?in that case how can i work >> around so that instead of killing sshd i just drop extra connections. Also >> interesting fact is drop_connections is not getting called? >> >> Thanks & Regards, >> Ravindranath >> >> On Wed, Jan 27, 2010 at 8:10 PM, Ben Lindstrom wrote: >> >>> >>> You really need to explain what you are doing as a DOS attack.. If all >>> you are doing is filling up the max unauthenticated connections this is a >>> known feature and you really should read the sshd_config manpage on >>> "MaxStartups" feature. >>> >>> - Ben >>> >>> >>> On Jan 27, 2010, at 12:51 AM, ravindra Chavalam wrote: >>> >>> > Hi, >>> > >>> > I am not sure to report this as a bug. so mailing to the list. >>> > >>> > >>> > I have sshd(openssh3.5p1) server running on my router and when i run >>> tcpjunk >>> > to that port, sshd gets killed after some time >>> > >>> > 192.168.71.1 is my sshd server and 192.168.71.4 is my client from where >>> i >>> > send my dos attack >>> > >>> > This is the tcpjunk command i gave to the ssh server >>> > >>> > #tcpjunk -s 192.168.71.1 -p 22 -c req -i 100 >>> > req session file contains string >>> > >>> > below attached is the netstat output. They are lot of these like these >>> but i >>> > just pasted two lines for reference >>> > >>> > #netstat -an|grep ":22" >>> > tcp 0 0 192.168.71.1:22 192.168.71.4:37757 TIME_WAIT >>> > tcp 0 0 192.168.71.1:22 192.168.71.4:55207 TIME_WAIT >>> > ... >>> > ... >>> > >>> > ... >>> > >>> > ... >>> > >>> > >>> > Can any one on tell me where in the openssh code i have to search to >>> find >>> > out the root cause for this issue >>> > >>> > >>> > Thanks a lot in advance >>> > _______________________________________________ >>> > openssh-unix-dev mailing list >>> > openssh-unix-dev at mindrot.org >>> > https://lists.mindrot.org/mailman/listinfo/openssh-unix-dev >>> >>> >> > From imorgan at nas.nasa.gov Fri Jan 29 06:15:46 2010 From: imorgan at nas.nasa.gov (Iain Morgan) Date: Thu, 28 Jan 2010 11:15:46 -0800 Subject: Possible issue with stdio forwarding Message-ID: <20100128191546.GA17027@linux55.nas.nasa.gov> Greetings, I've been doing a little testing with the stdio forwarding support added in recent snapshots and have encountered one possible issue. First, I should say that this feature generally seems to work. However, I haven't been able to get it to work when connecting to a server running SSH.COM's product. The config file I am using is fairly simple: Host sfe1 LogLevel debug3 Host cfe? ProxyCommand ssh -W %h:%p sfe1 Host * HostbasedAuthentication no If I connect to the bastion, sfe1, and request TCP port forwarding a la -L 2122:cfe1:22, I am able to successfully connect to cfe1 via the forwarded port. However, if I use stdio forwarding as indicated in the config file above, the connection fails. Given that the server allows TCP forwarding of the desired port, I had expected that stdio forwarding would likewise work. The stdio forwarding apporach does work if the intermediate server is running OpenSSH, even a somewhat dated version. So I'm suspecting it is a quirk with the SSH.COM server. Here's the debug3 output from the proxy command attempting to do the stdio forwarding. debug2: ssh_connect: needpriv 0 debug1: Connecting to sfe1 [129.99.242.1] port 22. debug1: Connection established. debug1: could not open key file '/etc/ssh/ssh_host_key': Permission denied debug1: could not open key file '/etc/ssh/ssh_host_dsa_key': Permission denied debug1: could not open key file '/etc/ssh/ssh_host_rsa_key': Permission denied debug1: identity file /u/wk/imorgan/.ssh/identity type -1 debug3: Not a RSA1 key file /u/wk/imorgan/.ssh/id_rsa. debug2: key_type_from_name: unknown key type '-----BEGIN' debug3: key_read: missing keytype debug2: key_type_from_name: unknown key type 'Proc-Type:' debug3: key_read: missing keytype debug2: key_type_from_name: unknown key type 'DEK-Info:' debug3: key_read: missing keytype debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug2: key_type_from_name: unknown key type '-----END' debug3: key_read: missing keytype debug1: identity file /u/wk/imorgan/.ssh/id_rsa type 1 debug1: identity file /u/wk/imorgan/.ssh/id_dsa type -1 debug1: Remote protocol version 2.0, remote software version 6.0.6.19 SSH Tectia Server debug1: no match: 6.0.6.19 SSH Tectia Server debug1: Enabling compatibility mode for protocol 2.0 debug1: Local version string SSH-2.0-OpenSSH_5.3 debug2: fd 3 setting O_NONBLOCK debug1: SSH2_MSG_KEXINIT sent debug1: SSH2_MSG_KEXINIT received debug2: kex_parse_kexinit: diffie-hellman-group-exchange-sha256,diffie-hellman-group-exchange-sha1,diffie-hellman-group14-sha1,diffie-hellman-group1-sha1 debug2: kex_parse_kexinit: ssh-rsa,ssh-dss debug2: kex_parse_kexinit: arcfour,blowfish-cbc,cast128-cbc,aes128-cbc,3des-cbc debug2: kex_parse_kexinit: arcfour,blowfish-cbc,cast128-cbc,aes128-cbc,3des-cbc debug2: kex_parse_kexinit: hmac-md5,hmac-sha1,umac-64 at openssh.com,hmac-ripemd160,hmac-ripemd160 at openssh.com,hmac-sha1-96,hmac-md5-96 debug2: kex_parse_kexinit: hmac-md5,hmac-sha1,umac-64 at openssh.com,hmac-ripemd160,hmac-ripemd160 at openssh.com,hmac-sha1-96,hmac-md5-96 debug2: kex_parse_kexinit: none,zlib at openssh.com,zlib debug2: kex_parse_kexinit: none,zlib at openssh.com,zlib debug2: kex_parse_kexinit: debug2: kex_parse_kexinit: debug2: kex_parse_kexinit: first_kex_follows 0 debug2: kex_parse_kexinit: reserved 0 debug2: kex_parse_kexinit: diffie-hellman-group1-sha1 debug2: kex_parse_kexinit: ssh-rsa debug2: kex_parse_kexinit: aes128-cbc,aes192-cbc,aes256-cbc,3des-cbc debug2: kex_parse_kexinit: aes128-cbc,aes192-cbc,aes256-cbc,3des-cbc debug2: kex_parse_kexinit: hmac-sha1 debug2: kex_parse_kexinit: hmac-sha1 debug2: kex_parse_kexinit: none,zlib debug2: kex_parse_kexinit: none,zlib debug2: kex_parse_kexinit: debug2: kex_parse_kexinit: debug2: kex_parse_kexinit: first_kex_follows 0 debug2: kex_parse_kexinit: reserved 0 debug2: mac_setup: found hmac-sha1 debug1: kex: server->client aes128-cbc hmac-sha1 none debug2: mac_setup: found hmac-sha1 debug1: kex: client->server aes128-cbc hmac-sha1 none debug2: dh_gen_key: priv key bits set: 159/320 debug2: bits set: 516/1024 debug1: sending SSH2_MSG_KEXDH_INIT debug1: expecting SSH2_MSG_KEXDH_REPLY debug3: check_host_in_hostfile: host sfe1 filename /u/wk/imorgan/.ssh/known_hosts debug3: check_host_in_hostfile: host sfe1 filename /etc/ssh/ssh_known_hosts debug3: check_host_in_hostfile: match line 896 debug3: check_host_in_hostfile: host 129.99.242.1 filename /u/wk/imorgan/.ssh/known_hosts debug3: check_host_in_hostfile: host 129.99.242.1 filename /etc/ssh/ssh_known_hosts debug3: check_host_in_hostfile: match line 896 debug1: Host 'sfe1' is known and matches the RSA host key. debug1: Found key in /etc/ssh/ssh_known_hosts:896 debug2: bits set: 528/1024 debug1: ssh_rsa_verify: signature correct debug2: kex_derive_keys debug2: set_newkeys: mode 1 debug1: SSH2_MSG_NEWKEYS sent debug1: expecting SSH2_MSG_NEWKEYS debug2: set_newkeys: mode 0 debug1: SSH2_MSG_NEWKEYS received debug1: Roaming not allowed by server debug1: SSH2_MSG_SERVICE_REQUEST sent debug3: Received SSH2_MSG_IGNORE debug3: Received SSH2_MSG_IGNORE debug2: service_accept: ssh-userauth debug1: SSH2_MSG_SERVICE_ACCEPT received debug2: key: /u/wk/imorgan/.ssh/id_rsa (0x1be0780) debug2: key: /u/wk/imorgan/.ssh/identity ((nil)) debug2: key: /u/wk/imorgan/.ssh/id_dsa ((nil)) debug3: Received SSH2_MSG_IGNORE debug3: input_userauth_banner ---------------------------------------------------------------- WARNING! This is a US Government computer. This system is for the use of authorized users only. By accessing and using the computer system you are consenting to system monitoring, including the monitoring of keystrokes. Unauthorized use of, or access to, this computer system may subject you to disciplinary action and criminal prosecution. ---------------------------------------------------------------- debug3: Received SSH2_MSG_IGNORE debug1: Authentications that can continue: keyboard-interactive debug3: start over, passed a different list keyboard-interactive debug3: preferred publickey,keyboard-interactive,password debug3: authmethod_lookup keyboard-interactive debug3: remaining preferred: password debug3: authmethod_is_enabled keyboard-interactive debug1: Next authentication method: keyboard-interactive debug2: userauth_kbdint debug2: we sent a keyboard-interactive packet, wait for reply debug3: Received SSH2_MSG_IGNORE debug2: input_userauth_info_req PAM Authentication debug2: input_userauth_info_req: num_prompts 1 debug3: packet_send2: adding 32 (len 26 padlen 6 extra_pad 64) debug3: Received SSH2_MSG_IGNORE Authenticated with partial success. debug1: Authentications that can continue: password,publickey debug3: start over, passed a different list password,publickey debug3: preferred publickey,keyboard-interactive,password debug3: authmethod_lookup publickey debug3: remaining preferred: keyboard-interactive,password debug3: authmethod_is_enabled publickey debug1: Next authentication method: publickey debug1: Offering public key: /u/wk/imorgan/.ssh/id_rsa debug3: send_pubkey_test debug2: we sent a publickey packet, wait for reply debug3: Received SSH2_MSG_IGNORE debug1: Server accepts key: pkalg ssh-rsa blen 533 debug2: input_userauth_pk_ok: fp 46:bb:2a:c2:29:72:ad:bc:a7:04:c8:8d:77:31:6f:5e debug3: sign_and_send_pubkey debug3: Received SSH2_MSG_IGNORE debug1: Authentication succeeded (publickey). debug3: client_setup_stdio_fwd cfe1:22 debug1: channel_connect_stdio_fwd cfe1:22 debug1: channel 0: new [stdio-forward] debug2: fd 4 setting O_NONBLOCK debug2: fd 5 setting O_NONBLOCK debug1: getpeername failed: Bad file descriptor debug1: Entering interactive session. debug3: Received SSH2_MSG_IGNORE channel 0: open failed: connect failed: No description debug2: channel 0: zombie debug2: channel 0: gc: notify user debug1: stdio forwarding: done ssh_exchange_identification: Connection closed by remote host And, for comparison, here's the output of a connection to the server requesting TCP forwarding: debug2: ssh_connect: needpriv 0 debug1: Connecting to sfe1 [129.99.242.1] port 22. debug1: Connection established. debug1: could not open key file '/etc/ssh/ssh_host_key': Permission denied debug1: could not open key file '/etc/ssh/ssh_host_dsa_key': Permission denied debug1: could not open key file '/etc/ssh/ssh_host_rsa_key': Permission denied debug1: identity file /u/wk/imorgan/.ssh/identity type -1 debug3: Not a RSA1 key file /u/wk/imorgan/.ssh/id_rsa. debug2: key_type_from_name: unknown key type '-----BEGIN' debug3: key_read: missing keytype debug2: key_type_from_name: unknown key type 'Proc-Type:' debug3: key_read: missing keytype debug2: key_type_from_name: unknown key type 'DEK-Info:' debug3: key_read: missing keytype debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug3: key_read: missing whitespace debug2: key_type_from_name: unknown key type '-----END' debug3: key_read: missing keytype debug1: identity file /u/wk/imorgan/.ssh/id_rsa type 1 debug1: identity file /u/wk/imorgan/.ssh/id_dsa type -1 debug1: Remote protocol version 2.0, remote software version 6.0.6.19 SSH Tectia Server debug1: no match: 6.0.6.19 SSH Tectia Server debug1: Enabling compatibility mode for protocol 2.0 debug1: Local version string SSH-2.0-OpenSSH_5.3 debug2: fd 3 setting O_NONBLOCK debug1: SSH2_MSG_KEXINIT sent debug1: SSH2_MSG_KEXINIT received debug2: kex_parse_kexinit: diffie-hellman-group-exchange-sha256,diffie-hellman-group-exchange-sha1,diffie-hellman-group14-sha1,diffie-hellman-group1-sha1 debug2: kex_parse_kexinit: ssh-rsa,ssh-dss debug2: kex_parse_kexinit: arcfour,blowfish-cbc,cast128-cbc,aes128-cbc,3des-cbc debug2: kex_parse_kexinit: arcfour,blowfish-cbc,cast128-cbc,aes128-cbc,3des-cbc debug2: kex_parse_kexinit: hmac-md5,hmac-sha1,umac-64 at openssh.com,hmac-ripemd160,hmac-ripemd160 at openssh.com,hmac-sha1-96,hmac-md5-96 debug2: kex_parse_kexinit: hmac-md5,hmac-sha1,umac-64 at openssh.com,hmac-ripemd160,hmac-ripemd160 at openssh.com,hmac-sha1-96,hmac-md5-96 debug2: kex_parse_kexinit: none,zlib at openssh.com,zlib debug2: kex_parse_kexinit: none,zlib at openssh.com,zlib debug2: kex_parse_kexinit: debug2: kex_parse_kexinit: debug2: kex_parse_kexinit: first_kex_follows 0 debug2: kex_parse_kexinit: reserved 0 debug2: kex_parse_kexinit: diffie-hellman-group1-sha1 debug2: kex_parse_kexinit: ssh-rsa debug2: kex_parse_kexinit: aes128-cbc,aes192-cbc,aes256-cbc,3des-cbc debug2: kex_parse_kexinit: aes128-cbc,aes192-cbc,aes256-cbc,3des-cbc debug2: kex_parse_kexinit: hmac-sha1 debug2: kex_parse_kexinit: hmac-sha1 debug2: kex_parse_kexinit: none,zlib debug2: kex_parse_kexinit: none,zlib debug2: kex_parse_kexinit: debug2: kex_parse_kexinit: debug2: kex_parse_kexinit: first_kex_follows 0 debug2: kex_parse_kexinit: reserved 0 debug2: mac_setup: found hmac-sha1 debug1: kex: server->client aes128-cbc hmac-sha1 none debug2: mac_setup: found hmac-sha1 debug1: kex: client->server aes128-cbc hmac-sha1 none debug2: dh_gen_key: priv key bits set: 161/320 debug2: bits set: 502/1024 debug1: sending SSH2_MSG_KEXDH_INIT debug1: expecting SSH2_MSG_KEXDH_REPLY debug3: check_host_in_hostfile: host sfe1 filename /u/wk/imorgan/.ssh/known_hosts debug3: check_host_in_hostfile: host sfe1 filename /etc/ssh/ssh_known_hosts debug3: check_host_in_hostfile: match line 896 debug3: check_host_in_hostfile: host 129.99.242.1 filename /u/wk/imorgan/.ssh/known_hosts debug3: check_host_in_hostfile: host 129.99.242.1 filename /etc/ssh/ssh_known_hosts debug3: check_host_in_hostfile: match line 896 debug1: Host 'sfe1' is known and matches the RSA host key. debug1: Found key in /etc/ssh/ssh_known_hosts:896 debug2: bits set: 522/1024 debug1: ssh_rsa_verify: signature correct debug2: kex_derive_keys debug2: set_newkeys: mode 1 debug1: SSH2_MSG_NEWKEYS sent debug1: expecting SSH2_MSG_NEWKEYS debug2: set_newkeys: mode 0 debug1: SSH2_MSG_NEWKEYS received debug1: Roaming not allowed by server debug1: SSH2_MSG_SERVICE_REQUEST sent debug3: Received SSH2_MSG_IGNORE debug3: Received SSH2_MSG_IGNORE debug2: service_accept: ssh-userauth debug1: SSH2_MSG_SERVICE_ACCEPT received debug2: key: /u/wk/imorgan/.ssh/id_rsa (0x149ca780) debug2: key: /u/wk/imorgan/.ssh/identity ((nil)) debug2: key: /u/wk/imorgan/.ssh/id_dsa ((nil)) debug3: Received SSH2_MSG_IGNORE debug3: input_userauth_banner ---------------------------------------------------------------- WARNING! This is a US Government computer. This system is for the use of authorized users only. By accessing and using the computer system you are consenting to system monitoring, including the monitoring of keystrokes. Unauthorized use of, or access to, this computer system may subject you to disciplinary action and criminal prosecution. ---------------------------------------------------------------- debug3: Received SSH2_MSG_IGNORE debug1: Authentications that can continue: keyboard-interactive debug3: start over, passed a different list keyboard-interactive debug3: preferred publickey,keyboard-interactive,password debug3: authmethod_lookup keyboard-interactive debug3: remaining preferred: password debug3: authmethod_is_enabled keyboard-interactive debug1: Next authentication method: keyboard-interactive debug2: userauth_kbdint debug2: we sent a keyboard-interactive packet, wait for reply debug3: Received SSH2_MSG_IGNORE debug2: input_userauth_info_req PAM Authentication debug2: input_userauth_info_req: num_prompts 1 debug3: packet_send2: adding 32 (len 26 padlen 6 extra_pad 64) debug3: Received SSH2_MSG_IGNORE Authenticated with partial success. debug1: Authentications that can continue: password,publickey debug3: start over, passed a different list password,publickey debug3: preferred publickey,keyboard-interactive,password debug3: authmethod_lookup publickey debug3: remaining preferred: keyboard-interactive,password debug3: authmethod_is_enabled publickey debug1: Next authentication method: publickey debug1: Offering public key: /u/wk/imorgan/.ssh/id_rsa debug3: send_pubkey_test debug2: we sent a publickey packet, wait for reply debug3: Received SSH2_MSG_IGNORE debug1: Server accepts key: pkalg ssh-rsa blen 533 debug2: input_userauth_pk_ok: fp 46:bb:2a:c2:29:72:ad:bc:a7:04:c8:8d:77:31:6f:5e debug3: sign_and_send_pubkey debug3: Received SSH2_MSG_IGNORE debug1: Authentication succeeded (publickey). debug1: Local connections to LOCALHOST:2122 forwarded to remote address cfe1:22 debug3: channel_setup_fwd_listener: type 2 wildcard 0 addr NULL debug1: Local forwarding listening on 127.0.0.1 port 2122. debug2: fd 4 setting O_NONBLOCK debug3: fd 4 is O_NONBLOCK debug1: channel 0: new [port listener] debug2: fd 7 setting O_NONBLOCK debug1: channel 1: new [client-session] debug3: ssh_session2_open: channel_new: 1 debug2: channel 1: send open debug1: Entering interactive session. debug3: Received SSH2_MSG_IGNORE debug2: callback start debug2: x11_get_proto: /usr/bin/xauth list :0.0 2>/dev/null Warning: No xauth data; using fake authentication data for X11 forwarding. debug1: Requesting X11 forwarding with authentication spoofing. debug2: channel 1: request x11-req confirm 0 debug1: Requesting authentication agent forwarding. debug2: channel 1: request auth-agent-req at openssh.com confirm 0 debug2: client_session2_setup: id 1 debug2: channel 1: request pty-req confirm 1 debug2: channel 1: request shell confirm 1 debug2: fd 3 setting TCP_NODELAY debug2: callback done debug2: channel 1: open confirm rwindow 65536 rmax 4096 debug3: Received SSH2_MSG_IGNORE debug2: channel_input_status_confirm: type 99 id 1 debug2: PTY allocation request accepted on channel 1 debug3: Received SSH2_MSG_IGNORE debug2: channel_input_status_confirm: type 99 id 1 debug2: shell request accepted on channel 1 debug3: Received SSH2_MSG_IGNORE debug1: Connection to port 2122 forwarding to cfe1 port 22 requested. debug2: fd 8 setting TCP_NODELAY debug2: fd 8 setting O_NONBLOCK debug3: fd 8 is O_NONBLOCK debug1: channel 2: new [direct-tcpip] debug3: Received SSH2_MSG_IGNORE debug2: channel 2: open confirm rwindow 65536 rmax 4096 debug3: Received SSH2_MSG_IGNORE debug3: Received SSH2_MSG_IGNORE debug3: Received SSH2_MSG_IGNORE debug3: Received SSH2_MSG_IGNORE debug2: channel 2: read<=0 rfd 8 len 0 debug2: channel 2: read failed debug2: channel 2: close_read debug2: channel 2: input open -> drain debug2: channel 2: ibuf empty debug2: channel 2: send eof debug2: channel 2: input drain -> closed debug3: Received SSH2_MSG_IGNORE debug2: channel 2: rcvd close debug2: channel 2: output open -> drain debug3: channel 2: will not send data after close debug2: channel 2: obuf empty debug2: channel 2: close_write debug2: channel 2: output drain -> closed debug2: channel 2: send close debug2: channel 2: is dead debug2: channel 2: garbage collecting debug1: channel 2: free: direct-tcpip: listening port 2122 for cfe1 port 22, connect from 127.0.0.1 port 51329, nchannels 3 debug3: channel 2: status: The following connections are open: #1 client-session (t4 r0 i0/0 o0/0 fd 5/6 cc -1) #2 direct-tcpip: listening port 2122 for cfe1 port 22, connect from 127.0.0.1 port 51329 (t4 r1 i3/0 o3/0 fd 8/8 cc -1) debug3: channel 2: close_fds r 8 w 8 e -1 debug3: Received SSH2_MSG_IGNORE debug3: Received SSH2_MSG_IGNORE debug3: Received SSH2_MSG_IGNORE debug3: Received SSH2_MSG_IGNORE debug3: Received SSH2_MSG_IGNORE debug3: Received SSH2_MSG_IGNORE debug1: client_input_channel_req: channel 1 rtype exit-status reply 0 debug2: channel 1: rcvd close debug2: channel 1: output open -> drain debug2: channel 1: close_read debug2: channel 1: input open -> closed debug3: channel 1: will not send data after close debug2: channel 1: obuf empty debug2: channel 1: close_write debug2: channel 1: output drain -> closed debug2: channel 1: almost dead debug2: channel 1: gc: notify user debug2: channel 1: gc: user detached debug2: channel 1: send close debug2: channel 1: is dead debug2: channel 1: garbage collecting debug1: channel 1: free: client-session, nchannels 2 debug3: channel 1: status: The following connections are open: #1 client-session (t4 r0 i3/0 o3/0 fd -1/-1 cc -1) debug3: channel 1: close_fds r -1 w -1 e 7 debug1: channel 0: free: port listener, nchannels 1 debug3: channel 0: status: The following connections are open: debug3: channel 0: close_fds r 4 w 4 e -1 debug1: fd 2 clearing O_NONBLOCK Connection to sfe1 closed. Transferred: sent 4680, received 9376 bytes, in 59.6 seconds Bytes per second: sent 78.5, received 157.2 debug1: Exit status 0 -- Iain Morgan From hlein at korelogic.com Fri Jan 29 08:59:45 2010 From: hlein at korelogic.com (Hank Leininger) Date: Thu, 28 Jan 2010 16:59:45 -0500 Subject: Repost: [patch] Automatically add keys to agent Message-ID: <20100128215945.GK30476@marklar.spinoli.org> On Mon, Jan 18, 2010 Joachim Schipper wrote: > What this patch does can be described as follows: > > Without: > you at local$ ssh somehost > Enter passphrase for RSA key 'foo': > you at somehost$ exit > $ ssh otherhost > Enter passphrase for RSA key 'foo': > you at otherhost$ > > With: > you at local$ ssh somehost > Enter passphrase for RSA key 'foo': > you at somehost$ exit > $ ssh otherhost > you at otherhost$ > > That is, it means you don't have to type the passphrase twice. This sounds very convenient. It also sounds very dangerous. Imagine an attacker has access to your account on a target system. They modify your authorized_keys file to add a command="" (or muck with your .bashrc or similar) to run this script when you connect: #!/bin/bash stty -echo echo -n "Enter passphrase for RSA key 'foo': " read GOTCHA stty echo echo echo "gotcha, passphrase is: '$GOTCHA'" [ And of course a real attack would stash or forward your passphrase, and just exec a shell so you think everything's normal. ] This is a concern with regular ssh setups as well: any time you ssh to a remote host using a passphrase-protected key, the remote host may try feeding you a bogus prompt and you might fall for it, thus giving away your passphrase (which is one of the problems with password-auth that key-auth is supposed to improve on). The ways to avoid ever falling into this trap: 1) Always ssh with -v, and read the verbose messages every time, so you are certain you know where the prompt originated. Not likely. 2) Always ssh-add your passphrases locally first, before ssh'ing anywhere. For best results, set BatchMode=yes by default in ~/.ssh/config so that you will never ever ever be prompted legitimately; the connection will simply fail until you remember to ssh-add. Therefore any time you are ever prompted when ssh'ing somewhere, you are being messed with. Your patch undermines 2). If it became a standard practice to "transparently add a passphrase to the agent the first time a key is used", then people will get used to the behavior that they sometimes have to enter their passphrase when ssh'ing somewhere, and sometimes don't. That will make them more willing victims. It's like sending users "secure" self-extracting encrypted archives, teaching people that it's sometimes OK after all to execute .exe's they receive in emails--undermines best-practice training and will end badly. -- Hank Leininger BE5D FCCA 673B D18B 98A9 3175 896E 3D4A 1B4D C5AC -------------- next part -------------- A non-text attachment was scrubbed... Name: not available Type: application/pgp-signature Size: 447 bytes Desc: not available URL: From jcs at jcs.org Fri Jan 29 12:05:42 2010 From: jcs at jcs.org (joshua stein) Date: Thu, 28 Jan 2010 18:05:42 -0700 Subject: Repost: [patch] Automatically add keys to agent In-Reply-To: <20100128215945.GK30476@marklar.spinoli.org> References: <20100128215945.GK30476@marklar.spinoli.org> Message-ID: <20100129010542.GC16261@humble.superblock.net> > Imagine an attacker has access to your account on a target system. then all bets are off anyway. > The ways to avoid ever falling into this trap: > > 1) Always ssh with -v, and read the verbose messages every time, so you > are certain you know where the prompt originated. Not likely. > > 2) Always ssh-add your passphrases locally first, before ssh'ing > anywhere. For best results, set BatchMode=yes by default in > ~/.ssh/config so that you will never ever ever be prompted > legitimately; the connection will simply fail until you remember > to ssh-add. Therefore any time you are ever prompted when ssh'ing > somewhere, you are being messed with. 3) don't turn the option on. nobody's proposing that it be on by default. From djm at mindrot.org Fri Jan 29 13:01:08 2010 From: djm at mindrot.org (Damien Miller) Date: Fri, 29 Jan 2010 13:01:08 +1100 (EST) Subject: Possible issue with stdio forwarding In-Reply-To: <20100128191546.GA17027@linux55.nas.nasa.gov> References: <20100128191546.GA17027@linux55.nas.nasa.gov> Message-ID: On Thu, 28 Jan 2010, Iain Morgan wrote: > Greetings, > > I've been doing a little testing with the stdio forwarding support added > in recent snapshots and have encountered one possible issue. First, I > should say that this feature generally seems to work. However, I haven't > been able to get it to work when connecting to a server running > SSH.COM's product. > > The config file I am using is fairly simple: > > Host sfe1 > LogLevel debug3 > > Host cfe? > ProxyCommand ssh -W %h:%p sfe1 > > Host * > HostbasedAuthentication no > > If I connect to the bastion, sfe1, and request TCP port forwarding a la > -L 2122:cfe1:22, I am able to successfully connect to cfe1 via the > forwarded port. However, if I use stdio forwarding as indicated in the > config file above, the connection fails. > > Given that the server allows TCP forwarding of the desired port, I had > expected that stdio forwarding would likewise work. The stdio forwarding > apporach does work if the intermediate server is running OpenSSH, even a > somewhat dated version. So I'm suspecting it is a quirk with the SSH.COM > server. > > Here's the debug3 output from the proxy command attempting to do the > stdio forwarding. ... > debug3: client_setup_stdio_fwd cfe1:22 > debug1: channel_connect_stdio_fwd cfe1:22 > debug1: channel 0: new [stdio-forward] > debug2: fd 4 setting O_NONBLOCK > debug2: fd 5 setting O_NONBLOCK > debug1: getpeername failed: Bad file descriptor > debug1: Entering interactive session. > debug3: Received SSH2_MSG_IGNORE > channel 0: open failed: connect failed: No description I think this is the culprit. From session.c: 1366 static void 1367 port_open_helper(Channel *c, char *rtype) 1368 { 1369 int direct; 1370 char buf[1024]; 1371 char *remote_ipaddr = get_peer_ipaddr(c->sock); 1372 int remote_port = get_peer_port(c->sock); ... 1386 packet_start(SSH2_MSG_CHANNEL_OPEN); 1387 packet_put_cstring(rtype); 1388 packet_put_int(c->self); 1389 packet_put_int(c->local_window_max); 1390 packet_put_int(c->local_maxpacket); ... 1400 /* originator host and port */ 1401 packet_put_cstring(remote_ipaddr); 1402 packet_put_int((u_int)remote_port); 1403 packet_send(); get_peer_ipaddr() will return "UNKNOWN" for stdio connections and get_peer_port() will return -1. OpenSSH just logs these values (they are client supplied, so untrustworthy for any serious use), but Tectia is probably sanity checking them. Could you try this diff? Index: channels.c =================================================================== RCS file: /cvs/src/usr.bin/ssh/channels.c,v retrieving revision 1.302 diff -u -p -r1.302 channels.c --- channels.c 26 Jan 2010 01:28:35 -0000 1.302 +++ channels.c 29 Jan 2010 01:59:34 -0000 @@ -1371,6 +1371,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 something */ + xfree(remote_ipaddr); + remote_ipaddr = xstrdup("127.0.0.1"); + remote_port = 1; + } + direct = (strcmp(rtype, "direct-tcpip") == 0); snprintf(buf, sizeof buf, I'm not sure this is the right solution. E.g. it doesn't try to match the AF of the target forward, but maybe it doesn't matter. -d From imorgan at nas.nasa.gov Fri Jan 29 15:33:43 2010 From: imorgan at nas.nasa.gov (Iain Morgan) Date: Thu, 28 Jan 2010 20:33:43 -0800 Subject: Possible issue with stdio forwarding In-Reply-To: References: <20100128191546.GA17027@linux55.nas.nasa.gov> Message-ID: <20100129043343.GC16079@linux55.nas.nasa.gov> On Thu, Jan 28, 2010 at 20:01:08 -0600, Damien Miller wrote: > On Thu, 28 Jan 2010, Iain Morgan wrote: > > > Greetings, > > > > I've been doing a little testing with the stdio forwarding support added > > in recent snapshots and have encountered one possible issue. First, I > > should say that this feature generally seems to work. However, I haven't > > been able to get it to work when connecting to a server running > > SSH.COM's product. > > > > The config file I am using is fairly simple: > > > > Host sfe1 > > LogLevel debug3 > > > > Host cfe? > > ProxyCommand ssh -W %h:%p sfe1 > > > > Host * > > HostbasedAuthentication no > > > > If I connect to the bastion, sfe1, and request TCP port forwarding a la > > -L 2122:cfe1:22, I am able to successfully connect to cfe1 via the > > forwarded port. However, if I use stdio forwarding as indicated in the > > config file above, the connection fails. > > > > Given that the server allows TCP forwarding of the desired port, I had > > expected that stdio forwarding would likewise work. The stdio forwarding > > apporach does work if the intermediate server is running OpenSSH, even a > > somewhat dated version. So I'm suspecting it is a quirk with the SSH.COM > > server. > > > > Here's the debug3 output from the proxy command attempting to do the > > stdio forwarding. > ... > > debug3: client_setup_stdio_fwd cfe1:22 > > debug1: channel_connect_stdio_fwd cfe1:22 > > debug1: channel 0: new [stdio-forward] > > debug2: fd 4 setting O_NONBLOCK > > debug2: fd 5 setting O_NONBLOCK > > debug1: getpeername failed: Bad file descriptor > > debug1: Entering interactive session. > > debug3: Received SSH2_MSG_IGNORE > > channel 0: open failed: connect failed: No description > > I think this is the culprit. From session.c: > > 1366 static void > 1367 port_open_helper(Channel *c, char *rtype) > 1368 { > 1369 int direct; > 1370 char buf[1024]; > 1371 char *remote_ipaddr = get_peer_ipaddr(c->sock); > 1372 int remote_port = get_peer_port(c->sock); > ... > 1386 packet_start(SSH2_MSG_CHANNEL_OPEN); > 1387 packet_put_cstring(rtype); > 1388 packet_put_int(c->self); > 1389 packet_put_int(c->local_window_max); > 1390 packet_put_int(c->local_maxpacket); > ... > 1400 /* originator host and port */ > 1401 packet_put_cstring(remote_ipaddr); > 1402 packet_put_int((u_int)remote_port); > 1403 packet_send(); > > get_peer_ipaddr() will return "UNKNOWN" for stdio connections and > get_peer_port() will return -1. OpenSSH just logs these values (they > are client supplied, so untrustworthy for any serious use), but Tectia > is probably sanity checking them. Could you try this diff? > > Index: channels.c > =================================================================== > RCS file: /cvs/src/usr.bin/ssh/channels.c,v > retrieving revision 1.302 > diff -u -p -r1.302 channels.c > --- channels.c 26 Jan 2010 01:28:35 -0000 1.302 > +++ channels.c 29 Jan 2010 01:59:34 -0000 > @@ -1371,6 +1371,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 something */ > + xfree(remote_ipaddr); > + remote_ipaddr = xstrdup("127.0.0.1"); > + remote_port = 1; > + } > + > direct = (strcmp(rtype, "direct-tcpip") == 0); > > snprintf(buf, sizeof buf, > > I'm not sure this is the right solution. E.g. it doesn't try to match > the AF of the target forward, but maybe it doesn't matter. > > -d I just tested it and it works. I'll do some further testing tomorrow. Thanks -- Iain Morgan From apb at cequrux.com Fri Jan 29 21:30:52 2010 From: apb at cequrux.com (Alan Barrett) Date: Fri, 29 Jan 2010 12:30:52 +0200 Subject: ssh(1) multiplexing rewrite In-Reply-To: References: <4B4F490C.70006@yahoo.com> Message-ID: <20100129103052.GD1397@apb-laptoy.apb.alt.za> On Sun, 24 Jan 2010, Damien Miller wrote: > I'm hoping to commit the attached version soon. Will the rewritten mux code make it easy to add a ControlPersist option? I currently use an unofficial patch that attempts to give the following semantics, but that has some bugs dealing with ungracefully terminated connections: ControlPersist When used in conjunction with ControlMaster, specifies that the master connection should remain open in the background (waiting for future client connections) after the initial client connec- tion has been closed. If set to ``no'', then the master connec- tion will not be placed into the background, and will close as soon as the initial client connection is closed. If set to ``yes'', then the master connection will remain in the background indefinitely (until killed or closed via a mechanism such as the ssh(1) ``-O exit'' option). If set to a time in seconds, or a time in any of the formats documented in sshd_config(5), then the backgrounded master connection will automatically terminate after it has remained idle (with no client connections) for the speci- fied time. --apb (Alan Barrett) From ravindra1103 at gmail.com Sat Jan 30 00:09:39 2010 From: ravindra1103 at gmail.com (ravindra Chavalam) Date: Fri, 29 Jan 2010 18:39:39 +0530 Subject: sshd killed due to dos attack In-Reply-To: References: Message-ID: Sorry for all the bad observations..Actual problem appears to be in sshd_exchange_identification function(that also i am not sure). When i attach the debugger i am getting a SIGPIPE signal sont know the exact location where the issue is generated because i am not able to set the source to my gdb Thanks Ravindranath On Thu, Jan 28, 2010 at 4:57 PM, ravindra Chavalam wrote: > Sorry for repetitive mails. But here is my one more observation > > -->First i made five successfull ssh connections to the sshd from my > local host.(It wont allow me to do the 6th one becase 5 are the max limit of > ssh connections) > > -->Then i ran the same tcpjunk command > > -->Now all the logs are filled with the below logs > > Jan 28 16:49:17 gateway user.info sshd: dropping connections > Jan 28 16:49:17 gateway user.info sshd: dropping connections > Jan 28 16:49:17 gateway user.info sshd: dropping connections > Jan 28 16:49:17 gateway user.info sshd: dropping connections > Jan 28 16:49:17 gateway user.info sshd: dropping connections > Jan 28 16:49:17 gateway user.info sshd: dropping connections > Jan 28 16:49:17 gateway user.info sshd: dropping connections > Jan 28 16:49:17 gateway user.info sshd: dropping connections > Jan 28 16:49:17 gateway user.info sshd: dropping connections > Jan 28 16:49:17 gateway user.info sshd: dropping connections > Jan 28 16:49:17 gateway user.info sshd: dropping connections > Jan 28 16:49:17 gateway user.info sshd: dropping connections > Jan 28 16:49:17 gateway user.info sshd: dropping connections > Jan 28 16:49:17 gateway user.info sshd: dropping connections > Jan 28 16:49:17 gateway user.info sshd: dropping connections > Jan 28 16:49:17 gateway user.info sshd: dropping connections > Jan 28 16:49:17 gateway user.info sshd: dropping connections > > also netstat-an|grep ":22" shows only > > tcp 6 0 0.0.0.0:22 0.0.0.0:* LISTEN > tcp 0 0 192.168.71.1:22 192.168.71.4:36320 > SYN_RECV > tcp 0 0 192.168.71.1:22 192.168.71.4:36246 > SYN_RECV > tcp 0 0 192.168.71.1:22 192.168.71.4:35994 > SYN_RECV > tcp 0 0 192.168.71.1:22 192.168.71.4:34288 > SYN_RECV > tcp 0 0 192.168.71.1:22 192.168.71.4:60815 > SYN_RECV > tcp 0 0 192.168.71.1:22 192.168.71.4:35582 > SYN_RECV > tcp 0 0 192.168.71.1:22 192.168.71.4:36102 > SYN_RECV > tcp 0 0 192.168.71.1:22 192.168.71.4:35821 > SYN_RECV > tcp 0 0 192.168.71.1:22 192.168.71.4:59029 > SYN_RECV > tcp 0 0 192.168.71.1:22 192.168.71.4:50535 > SYN_RECV > tcp 0 0 192.168.71.1:22 192.168.71.4:33056 > SYN_RECV > tcp 0 0 192.168.71.1:22 192.168.71.4:41566 > SYN_RECV > tcp 0 0 192.168.71.1:22 192.168.71.4:32920 > SYN_RECV > tcp 101 0 192.168.71.1:22 192.168.71.4:36455 > ESTABLISHED > tcp 101 0 192.168.71.1:22 192.168.71.4:36450 > ESTABLISHED > tcp 0 0 192.168.71.1:22 192.168.71.4:38544 > ESTABLISHED > tcp 0 0 192.168.71.1:22 192.168.71.4:60814 > FIN_WAIT2 > tcp 101 0 192.168.71.1:22 192.168.71.4:36453 > ESTABLISHED > tcp 101 0 192.168.71.1:22 192.168.71.4:36452 > ESTABLISHED > tcp 101 0 192.168.71.1:22 192.168.71.4:36449 > ESTABLISHED > tcp 101 0 192.168.71.1:22 192.168.71.4:36448 > ESTABLISHED > tcp 0 0 192.168.71.1:22 192.168.71.4:38546 > ESTABLISHED > tcp 0 0 192.168.71.1:22 192.168.71.4:33041 > FIN_WAIT2 > tcp 101 0 192.168.71.1:22 192.168.71.4:36451 > ESTABLISHED > tcp 0 0 192.168.71.1:22 192.168.71.4:45284 > FIN_WAIT2 > > Note:There are no TIME_WAIT as in previous case where sshd gets killed > -->Now when i ran multiple tcpjunk sessions also sshd is working properly > without any issues > > Hope this information gives some clues > > > Thanks & Regards, > Ravindranath > > > On Thu, Jan 28, 2010 at 3:42 PM, ravindra Chavalam > wrote: > >> Hi Ben, >> One more interesting observation. I kept this line before >> drop_connection(..) function call... >> syslog >> (LOG_INFO,"startups:%d,rate:%d,begin:%d,full:%d",startups,options.max_startups_rate,options.max_startups_begin,options.max_startups); >> if (drop_connection(startups) == 1) { >> >> >> and my log after running tcpjunk shows below log when the sshd got killed. >> So based on the parameters shown in the log and the way drop_connection is >> implemented it seems no way this fucnion is called. Note:startups is 0 >> always and drop_connection always retuns FALSE >> >> tail -f /var/log/messages >> Jan 28 15:30:24 gateway user.info sshd: >> startups:0,rate:100,begin:5,full:5 >> Jan 28 15:30:24 gateway user.info sshd: >> startups:0,rate:100,begin:5,full:5 >> Jan 28 15:30:24 gateway user.info sshd: >> startups:0,rate:100,begin:5,full:5 >> Jan 28 15:30:24 gateway user.info sshd: >> startups:0,rate:100,begin:5,full:5 >> Jan 28 15:30:24 gateway user.info sshd: >> startups:0,rate:100,begin:5,full:5 >> Jan 28 15:30:24 gateway user.info sshd: >> startups:0,rate:100,begin:5,full:5 >> Jan 28 15:30:24 gateway user.info sshd: >> startups:0,rate:100,begin:5,full:5 >> Jan 28 15:30:24 gateway user.info sshd: >> startups:0,rate:100,begin:5,full:5 >> Jan 28 15:30:24 gateway user.info sshd: >> startups:0,rate:100,begin:5,full:5 >> Jan 28 15:30:24 gateway user.info sshd: >> startups:0,rate:100,begin:5,full:5 >> >> >> Thanks & Regards, >> Ravindranath >> >> >> On Thu, Jan 28, 2010 at 3:00 PM, ravindra Chavalam < >> ravindra1103 at gmail.com> wrote: >> >>> Hi Ben, >>> >>> Thanks a lot for the response. I gave MaxStartups 10:30:60 (these are >>> defaults i suppose for our requirements). Still facing the same issue. Is >>> sshd getting killed is the expected behaviour?in that case how can i work >>> around so that instead of killing sshd i just drop extra connections. Also >>> interesting fact is drop_connections is not getting called? >>> >>> Thanks & Regards, >>> Ravindranath >>> >>> On Wed, Jan 27, 2010 at 8:10 PM, Ben Lindstrom wrote: >>> >>>> >>>> You really need to explain what you are doing as a DOS attack.. If all >>>> you are doing is filling up the max unauthenticated connections this is a >>>> known feature and you really should read the sshd_config manpage on >>>> "MaxStartups" feature. >>>> >>>> - Ben >>>> >>>> >>>> On Jan 27, 2010, at 12:51 AM, ravindra Chavalam wrote: >>>> >>>> > Hi, >>>> > >>>> > I am not sure to report this as a bug. so mailing to the list. >>>> > >>>> > >>>> > I have sshd(openssh3.5p1) server running on my router and when i run >>>> tcpjunk >>>> > to that port, sshd gets killed after some time >>>> > >>>> > 192.168.71.1 is my sshd server and 192.168.71.4 is my client from >>>> where i >>>> > send my dos attack >>>> > >>>> > This is the tcpjunk command i gave to the ssh server >>>> > >>>> > #tcpjunk -s 192.168.71.1 -p 22 -c req -i 100 >>>> > req session file contains string >>>> > >>>> > below attached is the netstat output. They are lot of these like these >>>> but i >>>> > just pasted two lines for reference >>>> > >>>> > #netstat -an|grep ":22" >>>> > tcp 0 0 192.168.71.1:22 192.168.71.4:37757 TIME_WAIT >>>> > tcp 0 0 192.168.71.1:22 192.168.71.4:55207 TIME_WAIT >>>> > ... >>>> > ... >>>> > >>>> > ... >>>> > >>>> > ... >>>> > >>>> > >>>> > Can any one on tell me where in the openssh code i have to search to >>>> find >>>> > out the root cause for this issue >>>> > >>>> > >>>> > Thanks a lot in advance >>>> > _______________________________________________ >>>> > openssh-unix-dev mailing list >>>> > openssh-unix-dev at mindrot.org >>>> > https://lists.mindrot.org/mailman/listinfo/openssh-unix-dev >>>> >>>> >>> >> > From imorgan at nas.nasa.gov Sat Jan 30 06:59:26 2010 From: imorgan at nas.nasa.gov (Iain Morgan) Date: Fri, 29 Jan 2010 11:59:26 -0800 Subject: Possible issue with stdio forwarding In-Reply-To: <20100129043343.GC16079@linux55.nas.nasa.gov> References: <20100128191546.GA17027@linux55.nas.nasa.gov> <20100129043343.GC16079@linux55.nas.nasa.gov> Message-ID: <20100129195926.GB17027@linux55.nas.nasa.gov> On Thu, Jan 28, 2010 at 22:33:43 -0600, Iain Morgan wrote: > On Thu, Jan 28, 2010 at 20:01:08 -0600, Damien Miller wrote: > > On Thu, 28 Jan 2010, Iain Morgan wrote: > > > > > Greetings, > > > > > > I've been doing a little testing with the stdio forwarding support added > > > in recent snapshots and have encountered one possible issue. First, I > > > should say that this feature generally seems to work. However, I haven't > > > been able to get it to work when connecting to a server running > > > SSH.COM's product. > > > [snip] > > > > I think this is the culprit. From session.c: > > > > 1366 static void > > 1367 port_open_helper(Channel *c, char *rtype) > > 1368 { > > 1369 int direct; > > 1370 char buf[1024]; > > 1371 char *remote_ipaddr = get_peer_ipaddr(c->sock); > > 1372 int remote_port = get_peer_port(c->sock); > > ... > > 1386 packet_start(SSH2_MSG_CHANNEL_OPEN); > > 1387 packet_put_cstring(rtype); > > 1388 packet_put_int(c->self); > > 1389 packet_put_int(c->local_window_max); > > 1390 packet_put_int(c->local_maxpacket); > > ... > > 1400 /* originator host and port */ > > 1401 packet_put_cstring(remote_ipaddr); > > 1402 packet_put_int((u_int)remote_port); > > 1403 packet_send(); > > > > get_peer_ipaddr() will return "UNKNOWN" for stdio connections and > > get_peer_port() will return -1. OpenSSH just logs these values (they > > are client supplied, so untrustworthy for any serious use), but Tectia > > is probably sanity checking them. Could you try this diff? > > > > Index: channels.c > > =================================================================== > > RCS file: /cvs/src/usr.bin/ssh/channels.c,v > > retrieving revision 1.302 > > diff -u -p -r1.302 channels.c > > --- channels.c 26 Jan 2010 01:28:35 -0000 1.302 > > +++ channels.c 29 Jan 2010 01:59:34 -0000 > > @@ -1371,6 +1371,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 something */ > > + xfree(remote_ipaddr); > > + remote_ipaddr = xstrdup("127.0.0.1"); > > + remote_port = 1; > > + } > > + > > direct = (strcmp(rtype, "direct-tcpip") == 0); > > > > snprintf(buf, sizeof buf, > > > > I'm not sure this is the right solution. E.g. it doesn't try to match > > the AF of the target forward, but maybe it doesn't matter. > > > > -d > > I just tested it and it works. I'll do some further testing tomorrow. > > Thanks > > -- > Iain Morgan Further testing against the 20100130 snapshot with the above patch applied confirms that it fixes the issue. No adverse effects have been detected. As an aside, I should mention that I am seeing chan_read_failed messages apparently from the master session when using multiplexing. These occur when the slave session exits. Here's an example message: channel 11: chan_read_failed for istate 3 It's not a big issue, but it may disturb some users. -- Iain Morgan From djm at mindrot.org Sat Jan 30 07:17:14 2010 From: djm at mindrot.org (Damien Miller) Date: Sat, 30 Jan 2010 07:17:14 +1100 (EST) Subject: Possible issue with stdio forwarding In-Reply-To: <20100129195926.GB17027@linux55.nas.nasa.gov> References: <20100128191546.GA17027@linux55.nas.nasa.gov> <20100129043343.GC16079@linux55.nas.nasa.gov> <20100129195926.GB17027@linux55.nas.nasa.gov> Message-ID: On Fri, 29 Jan 2010, Iain Morgan wrote: > As an aside, I should mention that I am seeing chan_read_failed messages > apparently from the master session when using multiplexing. These occur > when the slave session exits. Here's an example message: > > channel 11: chan_read_failed for istate 3 > > It's not a big issue, but it may disturb some users. Could you catch it with a debug trace on the master side? That would help immensely. -d From djm at mindrot.org Sat Jan 30 07:18:01 2010 From: djm at mindrot.org (Damien Miller) Date: Sat, 30 Jan 2010 07:18:01 +1100 (EST) Subject: ssh(1) multiplexing rewrite In-Reply-To: <20100129103052.GD1397@apb-laptoy.apb.alt.za> References: <4B4F490C.70006@yahoo.com> <20100129103052.GD1397@apb-laptoy.apb.alt.za> Message-ID: On Fri, 29 Jan 2010, Alan Barrett wrote: > On Sun, 24 Jan 2010, Damien Miller wrote: > > I'm hoping to commit the attached version soon. > > Will the rewritten mux code make it easy to add a ControlPersist option? not yet. I'd like to add it sometime though, but probably not for this release. -d From joachim at joachimschipper.nl Sun Jan 31 05:11:25 2010 From: joachim at joachimschipper.nl (Joachim Schipper) Date: Sat, 30 Jan 2010 19:11:25 +0100 Subject: "phishing" (was: [patch] Automatically add keys to agent) In-Reply-To: <20100128215945.GK30476@marklar.spinoli.org> References: <20100128215945.GK30476@marklar.spinoli.org> Message-ID: <20100130181124.GA6205@polymnia.sshunet.nl> -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 On Thu, Jan 28, 2010 at 04:59:45PM -0500, Hank Leininger wrote: > On Mon, Jan 18, 2010 Joachim Schipper wrote: > > What this patch does can be described as follows: > > > > Without: > > you at local$ ssh somehost > > Enter passphrase for RSA key 'foo': > > you at somehost$ exit > > $ ssh otherhost > > Enter passphrase for RSA key 'foo': > > you at otherhost$ > > > > With: > > you at local$ ssh somehost > > Enter passphrase for RSA key 'foo': > > you at somehost$ exit > > $ ssh otherhost > > you at otherhost$ > > > > That is, it means you don't have to type the passphrase twice. > > This sounds very convenient. > > It also sounds very dangerous. > > Imagine an attacker has access to your account on a target system. They > modify your authorized_keys [or .bashrc] to run [a script prompting > for a passphrase] when you connect. > This is a concern with regular ssh setups as well: [an attacker] may > try feeding you a bogus prompt and you might [enter your passphrase]. > The ways to avoid ever falling into this trap: > > 1) Always ssh with -v, and read the verbose messages every time, so you > are certain you know where the prompt originated. Not likely. > > 2) Always ssh-add your passphrases locally first, before ssh'ing > anywhere. For best results, set BatchMode=yes by default in > ~/.ssh/config so that you will never ever ever be prompted > legitimately; the connection will simply fail until you remember > to ssh-add. Therefore any time you are ever prompted when ssh'ing > somewhere, you are being messed with. > > Your patch undermines 2). If it became a standard practice to > "transparently add a passphrase to the agent the first time a key is > used", then people will get used to the behavior that they sometimes > have to enter their passphrase when ssh'ing somewhere, and sometimes > don't. That will make them more willing victims. (...) I'm sorry for the slow response; I'm in the last stage of writing my thesis, and that's always a busy time. If I understand you correctly, you argue that connecting to malicious hosts is currently secure, and will remain secure, but that it will become easier to convince people to send the passphrase for their key. You are right that this is a concern, but note that an attacker would only learn the passphrase to a key, which should be uninteresting without the key. More importantly, as you note, the current situation is no better. If you currently use keys, the attacker could send another 'Enter passphrase for ' message to obtain the passphrase. (And of course, if you currently use passwords, an attacker could obtain your password!) You are not wrong, but isn't this point applicable to a much wider range of cases than those covered by my patch? And do you know why it hasn't been addressed yet? Joachim -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.10 (OpenBSD) iQIcBAEBCAAGBQJLZHZMAAoJEIReuCyNazusoEEQAJ7APRDrg+nUr6+F/jgGE7Iy PReeJnTppkSNrIYUBVs0HEnCXIzEpVdqS8S3e1aguEjpqeT6wPnqwQHC/0oRCAa+ Wfv0XiSDYxQvsJA6H693eRgFJgqKBRHfJCEZuYQRxgr77qMSVuQ1GbxGxKG2QpHc pd9VuaIAUCrggUuBbrmKYQhpmWls5b68Qov4OFQFiT7f+MO3sjYbXUFPk1qNjnYO 4N0nN25dllzhQ4d29DZ8CktF30+wJV1H+7zJZmGgHxSt/ONKZayBLBSKOYShZjRE ffPIeQdmL/GBRL7A1FLMELOsteyM82KWFIMXMiL/fJfspwuQyKWIXQIgQ9aJZm8k g4fMMRNDCu/dg8cx5MvS4xLZJ9FmKUTNfKVRU4tj1FL03a2B+qVCLq7yv6UsBULc a3Bh+BXbjCOXMzZp8MYrYUezkyokH3m2txP9nMqsttAIHcS4mEl6sl0c9vZxSYtv i8vivNA47U+QMpL81hBQktTSknoCszYaphYn00xyIDWsJwc+yyTQj8I1kMXdfQ2T RWGZ7z/IsjQ3OrRmOKjQaawhexRsqJdlnDGvUZ1Ee1bWAq/ygvH1ine6uwPrHPhd DeFXte5N1BkiBk8p1qXRpnCraqMYShQSzYSwMJoD1DS69l0e/kVt17KF7iTSoQUU jLYc2Hd9QBiXL0/42u4E =VvvO -----END PGP SIGNATURE-----