Handle SSL negotiation errors more robustly

This commit adds some finer grained detail to handling the postmaster's
response to SSL negotiation packets, by accounting for the possibility
of an 'E' byte being sent back, and emitting an appropriate error.

In the naive case, the postmaster will respond with either 'S' (proceed
with an SSL connection) or 'N' (SSL is not supported). However, the
current if statement doesn't account for an 'E' byte being returned
by the postmaster, where an error is encountered (perhaps unable to
fork due to being out of memory).

By adding this case, we can prevent confusing error messages when SSL is
enforced and the postmaster returns an error after successful SSL
connections.

This also brings the connection handling further in line with
libpq, where 'E' is handled similarly as of this commit:

a49fbaaf8d

Given that there are no longer pre-7.0 databases out in the wild, I
believe this is a safe change to make, and should not break backwards
compatibility (unless matching on error message content).

* Replace if statement with switch, to catch 'S', 'E' and 'N' bytes
  returned by the postmaster
* Return an Error for non 'S' or 'N' cases
* Expand and restructure unit tests for SSL negotiation packets
This commit is contained in:
Matthew Blewitt 2018-02-12 21:35:21 +00:00 committed by Brian C
parent 0902d145f4
commit 5d32be4a90
2 changed files with 53 additions and 22 deletions

View File

@ -82,8 +82,13 @@ Connection.prototype.connect = function (port, host) {
this.stream.once('data', function (buffer) {
var responseCode = buffer.toString('utf8')
if (responseCode !== 'S') {
return self.emit('error', new Error('The server does not support SSL connections'))
switch (responseCode) {
case 'N':
return self.emit('error', new Error('The server does not support SSL connections'))
case 'S':
break
default:
return self.emit('error', new Error('There was an error establishing an SSL connection'))
}
var tls = require('tls')
self.stream = tls.connect({

View File

@ -37,26 +37,52 @@ suite.test('connection does not emit ECONNRESET errors during disconnect', funct
done()
})
var SSLNegotiationPacketTests = [
{
testName: 'connection does not emit ECONNRESET errors during disconnect also when using SSL',
errorMessage: null,
response: 'S',
responseType: 'sslconnect'
},
{
testName: 'connection emits an error when SSL is not supported',
errorMessage: 'The server does not support SSL connections',
response: 'N',
responseType: 'error'
},
{
testName: 'connection emits an error when postmaster responds to SSL negotiation packet',
errorMessage: 'There was an error establishing an SSL connection',
response: 'E',
responseType: 'error'
}
]
suite.test('connection does not emit ECONNRESET errors during disconnect also when using SSL', function (done) {
// our fake postgres server, which just responds with 'S' to start SSL
var socket
var server = net.createServer(function (c) {
socket = c
c.once('data', function (data) {
c.write(Buffer.from('S'))
for (var i = 0; i < SSLNegotiationPacketTests.length; i++) {
var tc = SSLNegotiationPacketTests[i]
suite.test(tc.testName, function (done) {
// our fake postgres server
var socket
var server = net.createServer(function (c) {
socket = c
c.once('data', function (data) {
c.write(Buffer.from(tc.response))
})
})
server.listen(7778, function () {
var con = new Connection({ssl: true})
con.connect(7778, 'localhost')
assert.emits(con, tc.responseType, function (err) {
if (err) {
assert.equal(err.message, tc.errorMessage)
}
con.end()
socket.destroy()
server.close()
done()
})
con.requestSsl()
})
})
server.listen(7778, function () {
var con = new Connection({ssl: true})
con.connect(7778, 'localhost')
assert.emits(con, 'sslconnect', function () {
con.end()
socket.destroy()
server.close()
done()
})
con.requestSsl()
})
})
}