1
/*****************************************************************************
2
* Copyright (C) 2008 EnterpriseDB Corporation.
3
* Copyright (C) 2011 Stado Global Development Group.
5
* This file is part of Stado.
7
* Stado is free software: you can redistribute it and/or modify
8
* it under the terms of the GNU General Public License as published by
9
* the Free Software Foundation, either version 3 of the License, or
10
* (at your option) any later version.
12
* Stado is distributed in the hope that it will be useful,
13
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
* GNU General Public License for more details.
17
* You should have received a copy of the GNU General Public License
18
* along with Stado. If not, see <http://www.gnu.org/licenses/>.
20
* You can find Stado at http://www.stado.us
22
****************************************************************************/
23
package org.postgresql.driver.core.v3;
25
import java.util.Properties;
28
import java.io.IOException;
29
import java.net.ConnectException;
31
import org.postgresql.driver.Driver;
32
import org.postgresql.driver.core.*;
33
import org.postgresql.driver.util.GT;
34
import org.postgresql.driver.util.MD5Digest;
35
import org.postgresql.driver.util.PSQLException;
36
import org.postgresql.driver.util.PSQLState;
37
import org.postgresql.driver.util.PSQLWarning;
38
import org.postgresql.driver.util.ServerErrorMessage;
39
import org.postgresql.driver.util.UnixCrypt;
42
* ConnectionFactory implementation for version 3 (7.4+) connections.
44
* @author Oliver Jowett (oliver@opencloud.com), based on the previous implementation
46
public class ConnectionFactoryImpl extends ConnectionFactory {
47
private static final int AUTH_REQ_OK = 0;
48
private static final int AUTH_REQ_KRB4 = 1;
49
private static final int AUTH_REQ_KRB5 = 2;
50
private static final int AUTH_REQ_PASSWORD = 3;
51
private static final int AUTH_REQ_CRYPT = 4;
52
private static final int AUTH_REQ_MD5 = 5;
53
private static final int AUTH_REQ_SCM = 6;
54
private static final int AUTH_REQ_GSS = 7;
55
private static final int AUTH_REQ_GSS_CONTINUE = 8;
56
private static final int AUTH_REQ_SSPI = 9;
58
/** Marker exception; thrown when we want to fall back to using V2. */
59
private static class UnsupportedProtocolException extends IOException {
62
public ProtocolConnection openConnectionImpl(String host, int port, String user, String database, Properties info, Logger logger) throws SQLException {
63
// Extract interesting values from the info properties:
65
boolean requireSSL = (info.getProperty("ssl") != null);
66
boolean trySSL = requireSSL; // XXX temporary until we revisit the ssl property values
68
// - the TCP keep alive setting
69
boolean requireTCPKeepAlive = (Boolean.valueOf(info.getProperty("tcpKeepAlive")).booleanValue());
71
// NOTE: To simplify this code, it is assumed that if we are
72
// using the V3 protocol, then the database is at least 7.4. That
73
// eliminates the need to check database versions and maintain
74
// backward-compatible code here.
76
// Change by Chris Smith <cdsmith@twu.net>
78
if (logger.logDebug())
79
logger.debug("Trying to establish a protocol version 3 connection to " + host + ":" + port);
82
// Establish a connection.
85
PGStream newStream = null;
88
newStream = new PGStream(host, port);
90
// Construct and send an ssl startup packet if requested.
92
newStream = enableSSL(newStream, requireSSL, info, logger);
94
// Set the socket timeout if the "socketTimeout" property has been set.
95
String socketTimeoutProperty = info.getProperty("socketTimeout", "0");
97
int socketTimeout = Integer.parseInt(socketTimeoutProperty);
98
if (socketTimeout > 0) {
99
newStream.getSocket().setSoTimeout(socketTimeout*1000);
101
} catch (NumberFormatException nfe) {
102
logger.info("Couldn't parse socketTimeout value:" + socketTimeoutProperty);
105
// Enable TCP keep-alive probe if required.
106
newStream.getSocket().setKeepAlive(requireTCPKeepAlive);
108
// Construct and send a startup packet.
109
String[][] params = {
111
{ "database", database },
112
{ "client_encoding", "UNICODE" },
113
{ "DateStyle", "ISO" },
114
{ "extra_float_digits", "2" }
117
sendStartupPacket(newStream, params, logger);
119
// Do authentication (until AuthenticationOk).
120
doAuthentication(newStream, host, user, info, logger);
123
ProtocolConnectionImpl protoConnection = new ProtocolConnectionImpl(newStream, user, database, info, logger);
124
readStartupMessages(newStream, protoConnection, logger);
127
return protoConnection;
129
catch (UnsupportedProtocolException upe)
131
// Swallow this and return null so ConnectionFactory tries the next protocol.
132
if (logger.logDebug())
133
logger.debug("Protocol not supported, abandoning connection.");
138
catch (IOException e)
143
catch (ConnectException cex)
145
// Added by Peter Mount <peter@retep.org.uk>
146
// ConnectException is thrown when the connection cannot be made.
147
// we trap this an return a more meaningful message for the end user
148
throw new PSQLException (GT.tr("Connection refused. Check that the hostname and port are correct and that the postmaster is accepting TCP/IP connections."), PSQLState.CONNECTION_REJECTED, cex);
150
catch (IOException ioe)
152
if (newStream != null)
158
catch (IOException e)
162
throw new PSQLException (GT.tr("The connection attempt failed."), PSQLState.CONNECTION_UNABLE_TO_CONNECT, ioe);
164
catch (SQLException se)
166
if (newStream != null)
172
catch (IOException e)
180
private PGStream enableSSL(PGStream pgStream, boolean requireSSL, Properties info, Logger logger) throws IOException, SQLException {
181
if (logger.logDebug())
182
logger.debug(" FE=> SSLRequest");
184
// Send SSL request packet
185
pgStream.SendInteger4(8);
186
pgStream.SendInteger2(1234);
187
pgStream.SendInteger2(5679);
190
// Now get the response from the backend, one of N, E, S.
191
int beresp = pgStream.ReceiveChar();
195
if (logger.logDebug())
196
logger.debug(" <=BE SSLError");
198
// Server doesn't even know about the SSL handshake protocol
200
throw new PSQLException(GT.tr("The server does not support SSL."), PSQLState.CONNECTION_FAILURE);
202
// We have to reconnect to continue.
204
return new PGStream(pgStream.getHost(), pgStream.getPort());
207
if (logger.logDebug())
208
logger.debug(" <=BE SSLRefused");
210
// Server does not support ssl
212
throw new PSQLException(GT.tr("The server does not support SSL."), PSQLState.CONNECTION_FAILURE);
217
if (logger.logDebug())
218
logger.debug(" <=BE SSLOk");
220
// Server supports ssl
221
org.postgresql.driver.ssl.MakeSSL.convert(pgStream, info, logger);
225
throw new PSQLException(GT.tr("An error occured while setting up the SSL connection."), PSQLState.CONNECTION_FAILURE);
229
private void sendStartupPacket(PGStream pgStream, String[][] params, Logger logger) throws IOException {
230
if (logger.logDebug())
233
for (int i = 0; i < params.length; ++i)
237
details += params[i][0] + "=" + params[i][1];
239
logger.debug(" FE=> StartupPacket(" + details + ")");
243
* Precalculate message length and encode params.
246
byte[][] encodedParams = new byte[params.length * 2][];
247
for (int i = 0; i < params.length; ++i)
249
encodedParams[i*2] = params[i][0].getBytes("UTF-8");
250
encodedParams[i*2 + 1] = params[i][1].getBytes("UTF-8");
251
length += encodedParams[i * 2].length + 1 + encodedParams[i * 2 + 1].length + 1;
254
length += 1; // Terminating \0
257
* Send the startup message.
259
pgStream.SendInteger4(length);
260
pgStream.SendInteger2(3); // protocol major
261
pgStream.SendInteger2(0); // protocol minor
262
for (int i = 0; i < encodedParams.length; ++i)
264
pgStream.Send(encodedParams[i]);
265
pgStream.SendChar(0);
268
pgStream.SendChar(0);
272
private void doAuthentication(PGStream pgStream, String host, String user, Properties info, Logger logger) throws IOException, SQLException
274
// Now get the response from the backend, either an error message
275
// or an authentication request
277
String password = info.getProperty("password");
281
int beresp = pgStream.ReceiveChar();
286
// An error occured, so pass the error message to the
289
// The most common one to be thrown here is:
290
// "User authentication failed"
292
int l_elen = pgStream.ReceiveInteger4();
295
// if the error length is > than 30000 we assume this is really a v2 protocol
296
// server, so trigger fallback.
297
throw new UnsupportedProtocolException();
300
ServerErrorMessage errorMsg = new ServerErrorMessage(pgStream.ReceiveString(l_elen - 4), logger.getLogLevel());
301
if (logger.logDebug())
302
logger.debug(" <=BE ErrorMessage(" + errorMsg + ")");
303
throw new PSQLException(errorMsg);
306
// Authentication request.
307
// Get the message length
308
int l_msgLen = pgStream.ReceiveInteger4();
310
// Get the type of request
311
int areq = pgStream.ReceiveInteger4();
313
// Process the request.
318
byte[] salt = pgStream.Receive(2);
320
if (logger.logDebug())
321
logger.debug(" <=BE AuthenticationReqCrypt(salt='" + new String(salt, "US-ASCII") + "')");
323
if (password == null)
324
throw new PSQLException(GT.tr("The server requested password-based authentication, but no password was provided."), PSQLState.CONNECTION_REJECTED);
326
byte[] encodedResult = UnixCrypt.crypt(salt, password.getBytes("UTF-8"));
328
if (logger.logDebug())
329
logger.debug(" FE=> Password(crypt='" + new String(encodedResult, "US-ASCII") + "')");
331
pgStream.SendChar('p');
332
pgStream.SendInteger4(4 + encodedResult.length + 1);
333
pgStream.Send(encodedResult);
334
pgStream.SendChar(0);
342
byte[] md5Salt = pgStream.Receive(4);
343
if (logger.logDebug())
345
logger.debug(" <=BE AuthenticationReqMD5(salt=" + Utils.toHexString(md5Salt) + ")");
348
if (password == null)
349
throw new PSQLException(GT.tr("The server requested password-based authentication, but no password was provided."), PSQLState.CONNECTION_REJECTED);
351
byte[] digest = MD5Digest.encode(user.getBytes("UTF-8"), password.getBytes("UTF-8"), md5Salt);
353
if (logger.logDebug())
355
logger.debug(" FE=> Password(md5digest=" + new String(digest, "US-ASCII") + ")");
358
pgStream.SendChar('p');
359
pgStream.SendInteger4(4 + digest.length + 1);
360
pgStream.Send(digest);
361
pgStream.SendChar(0);
367
case AUTH_REQ_PASSWORD:
369
if (logger.logDebug())
371
logger.debug(" <=BE AuthenticationReqPassword");
372
logger.debug(" FE=> Password(password=<not shown>)");
375
if (password == null)
376
throw new PSQLException(GT.tr("The server requested password-based authentication, but no password was provided."), PSQLState.CONNECTION_REJECTED);
378
byte[] encodedPassword = password.getBytes("UTF-8");
380
pgStream.SendChar('p');
381
pgStream.SendInteger4(4 + encodedPassword.length + 1);
382
pgStream.Send(encodedPassword);
383
pgStream.SendChar(0);
390
if (logger.logDebug())
391
logger.debug(" <=BE AuthenticationReqSSPI");
393
throw new PSQLException(GT.tr("SSPI authentication is not supported because it is not portable. Try configuring the server to use GSSAPI instead."), PSQLState.CONNECTION_REJECTED);
396
if (logger.logDebug())
397
logger.debug(" <=BE AuthenticationOk");
399
return ; // We're done.
402
if (logger.logDebug())
403
logger.debug(" <=BE AuthenticationReq (unsupported type " + ((int)areq) + ")");
405
throw new PSQLException(GT.tr("The authentication type {0} is not supported. Check that you have configured the pg_hba.conf file to include the client''s IP address or subnet, and that it is using an authentication scheme supported by the driver.", new Integer(areq)), PSQLState.CONNECTION_REJECTED);
411
throw new PSQLException(GT.tr("Protocol error. Session setup failed."), PSQLState.CONNECTION_UNABLE_TO_CONNECT);
416
private void readStartupMessages(PGStream pgStream, ProtocolConnectionImpl protoConnection, Logger logger) throws IOException, SQLException {
419
int beresp = pgStream.ReceiveChar();
423
// Ready For Query; we're done.
424
if (pgStream.ReceiveInteger4() != 5)
425
throw new IOException("unexpected length of ReadyForQuery packet");
427
char tStatus = (char)pgStream.ReceiveChar();
428
if (logger.logDebug())
429
logger.debug(" <=BE ReadyForQuery(" + tStatus + ")");
431
// Update connection state.
435
protoConnection.setTransactionState(ProtocolConnection.TRANSACTION_IDLE);
438
protoConnection.setTransactionState(ProtocolConnection.TRANSACTION_OPEN);
441
protoConnection.setTransactionState(ProtocolConnection.TRANSACTION_FAILED);
452
int l_msgLen = pgStream.ReceiveInteger4();
454
throw new PSQLException(GT.tr("Protocol error. Session setup failed."), PSQLState.CONNECTION_UNABLE_TO_CONNECT);
456
int pid = pgStream.ReceiveInteger4();
457
int ckey = pgStream.ReceiveInteger4();
459
if (logger.logDebug())
460
logger.debug(" <=BE BackendKeyData(pid=" + pid + ",ckey=" + ckey + ")");
462
protoConnection.setBackendKeyData(pid, ckey);
467
int l_elen = pgStream.ReceiveInteger4();
468
ServerErrorMessage l_errorMsg = new ServerErrorMessage(pgStream.ReceiveString(l_elen - 4), logger.getLogLevel());
470
if (logger.logDebug())
471
logger.debug(" <=BE ErrorMessage(" + l_errorMsg + ")");
473
throw new PSQLException(l_errorMsg);
477
int l_nlen = pgStream.ReceiveInteger4();
478
ServerErrorMessage l_warnMsg = new ServerErrorMessage(pgStream.ReceiveString(l_nlen - 4), logger.getLogLevel());
480
if (logger.logDebug())
481
logger.debug(" <=BE NoticeResponse(" + l_warnMsg + ")");
483
protoConnection.addWarning(new PSQLWarning(l_warnMsg));
488
int l_len = pgStream.ReceiveInteger4();
489
String name = pgStream.ReceiveString();
490
String value = pgStream.ReceiveString();
492
if (logger.logDebug())
493
logger.debug(" <=BE ParameterStatus(" + name + " = " + value + ")");
495
if (name.equals("server_version"))
496
protoConnection.setServerVersion(value);
497
else if (name.equals("client_encoding"))
499
if (!value.equals("UNICODE"))
500
throw new PSQLException(GT.tr("Protocol error. Session setup failed."), PSQLState.CONNECTION_UNABLE_TO_CONNECT);
501
pgStream.setEncoding(Encoding.getDatabaseEncoding("UNICODE"));
503
else if (name.equals("standard_conforming_strings"))
505
if (value.equals("on"))
506
protoConnection.setStandardConformingStrings(true);
507
else if (value.equals("off"))
508
protoConnection.setStandardConformingStrings(false);
510
throw new PSQLException(GT.tr("Protocol error. Session setup failed."), PSQLState.CONNECTION_UNABLE_TO_CONNECT);
516
if (logger.logDebug())
517
logger.debug("invalid message type=" + (char)beresp);
518
throw new PSQLException(GT.tr("Protocol error. Session setup failed."), PSQLState.CONNECTION_UNABLE_TO_CONNECT);