~sgdg/stado/stado25

« back to all changes in this revision

Viewing changes to src/org/postgresql/driver/core/v3/ConnectionFactoryImpl.java

  • Committer: Jim Mlodgenski
  • Date: 2011-08-30 22:39:37 UTC
  • mfrom: (1.1.3 stado)
  • Revision ID: jim@cirrusql.com-20110830223937-25q231a31x0e08b4
Merge from Spatial branch

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*****************************************************************************
 
2
 * Copyright (C) 2008 EnterpriseDB Corporation.
 
3
 * Copyright (C) 2011 Stado Global Development Group.
 
4
 *
 
5
 * This file is part of Stado.
 
6
 *
 
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.
 
11
 *
 
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.
 
16
 *
 
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/>.
 
19
 *
 
20
 * You can find Stado at http://www.stado.us
 
21
 *
 
22
 ****************************************************************************/
 
23
package org.postgresql.driver.core.v3;
 
24
 
 
25
import java.util.Properties;
 
26
 
 
27
import java.sql.*;
 
28
import java.io.IOException;
 
29
import java.net.ConnectException;
 
30
 
 
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;
 
40
 
 
41
/**
 
42
 * ConnectionFactory implementation for version 3 (7.4+) connections.
 
43
 *
 
44
 * @author Oliver Jowett (oliver@opencloud.com), based on the previous implementation
 
45
 */
 
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;
 
57
 
 
58
    /** Marker exception; thrown when we want to fall back to using V2. */
 
59
    private static class UnsupportedProtocolException extends IOException {
 
60
    }
 
61
 
 
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:
 
64
        //  - the SSL setting
 
65
        boolean requireSSL = (info.getProperty("ssl") != null);
 
66
        boolean trySSL = requireSSL; // XXX temporary until we revisit the ssl property values
 
67
 
 
68
        //  - the TCP keep alive setting
 
69
        boolean requireTCPKeepAlive = (Boolean.valueOf(info.getProperty("tcpKeepAlive")).booleanValue());
 
70
 
 
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.
 
75
        //
 
76
        // Change by Chris Smith <cdsmith@twu.net>
 
77
 
 
78
        if (logger.logDebug())
 
79
            logger.debug("Trying to establish a protocol version 3 connection to " + host + ":" + port);
 
80
 
 
81
        //
 
82
        // Establish a connection.
 
83
        //
 
84
 
 
85
        PGStream newStream = null;
 
86
        try
 
87
        {
 
88
            newStream = new PGStream(host, port);
 
89
 
 
90
            // Construct and send an ssl startup packet if requested.
 
91
            if (trySSL)
 
92
                newStream = enableSSL(newStream, requireSSL, info, logger);
 
93
            
 
94
            // Set the socket timeout if the "socketTimeout" property has been set.
 
95
            String socketTimeoutProperty = info.getProperty("socketTimeout", "0");
 
96
            try {
 
97
                int socketTimeout = Integer.parseInt(socketTimeoutProperty);
 
98
                if (socketTimeout > 0) {
 
99
                    newStream.getSocket().setSoTimeout(socketTimeout*1000);
 
100
                }
 
101
            } catch (NumberFormatException nfe) {
 
102
                logger.info("Couldn't parse socketTimeout value:" + socketTimeoutProperty);
 
103
            }
 
104
 
 
105
            // Enable TCP keep-alive probe if required.
 
106
            newStream.getSocket().setKeepAlive(requireTCPKeepAlive);
 
107
 
 
108
            // Construct and send a startup packet.
 
109
            String[][] params = {
 
110
                                    { "user", user },
 
111
                                    { "database", database },
 
112
                                    { "client_encoding", "UNICODE" },
 
113
                                    { "DateStyle", "ISO" },
 
114
                                    { "extra_float_digits", "2" }
 
115
                                };
 
116
 
 
117
            sendStartupPacket(newStream, params, logger);
 
118
 
 
119
            // Do authentication (until AuthenticationOk).
 
120
            doAuthentication(newStream, host, user, info, logger);
 
121
 
 
122
            // Do final startup.
 
123
            ProtocolConnectionImpl protoConnection = new ProtocolConnectionImpl(newStream, user, database, info, logger);
 
124
            readStartupMessages(newStream, protoConnection, logger);
 
125
 
 
126
            // And we're done.
 
127
            return protoConnection;
 
128
        }
 
129
        catch (UnsupportedProtocolException upe)
 
130
        {
 
131
            // Swallow this and return null so ConnectionFactory tries the next protocol.
 
132
            if (logger.logDebug())
 
133
                logger.debug("Protocol not supported, abandoning connection.");
 
134
            try
 
135
            {
 
136
                newStream.close();
 
137
            }
 
138
            catch (IOException e)
 
139
            {
 
140
            }
 
141
            return null;
 
142
        }
 
143
        catch (ConnectException cex)
 
144
        {
 
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);
 
149
        }
 
150
        catch (IOException ioe)
 
151
        {
 
152
            if (newStream != null)
 
153
            {
 
154
                try
 
155
                {
 
156
                    newStream.close();
 
157
                }
 
158
                catch (IOException e)
 
159
                {
 
160
                }
 
161
            }
 
162
            throw new PSQLException (GT.tr("The connection attempt failed."), PSQLState.CONNECTION_UNABLE_TO_CONNECT, ioe);
 
163
        }
 
164
        catch (SQLException se)
 
165
        {
 
166
            if (newStream != null)
 
167
            {
 
168
                try
 
169
                {
 
170
                    newStream.close();
 
171
                }
 
172
                catch (IOException e)
 
173
                {
 
174
                }
 
175
            }
 
176
            throw se;
 
177
        }
 
178
    }
 
179
 
 
180
    private PGStream enableSSL(PGStream pgStream, boolean requireSSL, Properties info, Logger logger) throws IOException, SQLException {
 
181
        if (logger.logDebug())
 
182
            logger.debug(" FE=> SSLRequest");
 
183
 
 
184
        // Send SSL request packet
 
185
        pgStream.SendInteger4(8);
 
186
        pgStream.SendInteger2(1234);
 
187
        pgStream.SendInteger2(5679);
 
188
        pgStream.flush();
 
189
 
 
190
        // Now get the response from the backend, one of N, E, S.
 
191
        int beresp = pgStream.ReceiveChar();
 
192
        switch (beresp)
 
193
        {
 
194
        case 'E':
 
195
            if (logger.logDebug())
 
196
                logger.debug(" <=BE SSLError");
 
197
 
 
198
            // Server doesn't even know about the SSL handshake protocol
 
199
            if (requireSSL)
 
200
                throw new PSQLException(GT.tr("The server does not support SSL."), PSQLState.CONNECTION_FAILURE);
 
201
 
 
202
            // We have to reconnect to continue.
 
203
            pgStream.close();
 
204
            return new PGStream(pgStream.getHost(), pgStream.getPort());
 
205
 
 
206
        case 'N':
 
207
            if (logger.logDebug())
 
208
                logger.debug(" <=BE SSLRefused");
 
209
 
 
210
            // Server does not support ssl
 
211
            if (requireSSL)
 
212
                throw new PSQLException(GT.tr("The server does not support SSL."), PSQLState.CONNECTION_FAILURE);
 
213
 
 
214
            return pgStream;
 
215
 
 
216
        case 'S':
 
217
            if (logger.logDebug())
 
218
                logger.debug(" <=BE SSLOk");
 
219
 
 
220
            // Server supports ssl
 
221
            org.postgresql.driver.ssl.MakeSSL.convert(pgStream, info, logger);
 
222
            return pgStream;
 
223
 
 
224
        default:
 
225
            throw new PSQLException(GT.tr("An error occured while setting up the SSL connection."), PSQLState.CONNECTION_FAILURE);
 
226
        }
 
227
    }
 
228
 
 
229
    private void sendStartupPacket(PGStream pgStream, String[][] params, Logger logger) throws IOException {
 
230
        if (logger.logDebug())
 
231
        {
 
232
            String details = "";
 
233
            for (int i = 0; i < params.length; ++i)
 
234
            {
 
235
                if (i != 0)
 
236
                    details += ", ";
 
237
                details += params[i][0] + "=" + params[i][1];
 
238
            }
 
239
            logger.debug(" FE=> StartupPacket(" + details + ")");
 
240
        }
 
241
 
 
242
        /*
 
243
         * Precalculate message length and encode params.
 
244
         */
 
245
        int length = 4 + 4;
 
246
        byte[][] encodedParams = new byte[params.length * 2][];
 
247
        for (int i = 0; i < params.length; ++i)
 
248
        {
 
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;
 
252
        }
 
253
 
 
254
        length += 1; // Terminating \0
 
255
 
 
256
        /*
 
257
         * Send the startup message.
 
258
         */
 
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)
 
263
        {
 
264
            pgStream.Send(encodedParams[i]);
 
265
            pgStream.SendChar(0);
 
266
        }
 
267
 
 
268
        pgStream.SendChar(0);
 
269
        pgStream.flush();
 
270
    }
 
271
 
 
272
    private void doAuthentication(PGStream pgStream, String host, String user, Properties info, Logger logger) throws IOException, SQLException
 
273
    {
 
274
        // Now get the response from the backend, either an error message
 
275
        // or an authentication request
 
276
 
 
277
        String password = info.getProperty("password");
 
278
 
 
279
        while (true)
 
280
        {
 
281
            int beresp = pgStream.ReceiveChar();
 
282
 
 
283
            switch (beresp)
 
284
            {
 
285
            case 'E':
 
286
                // An error occured, so pass the error message to the
 
287
                // user.
 
288
                //
 
289
                // The most common one to be thrown here is:
 
290
                // "User authentication failed"
 
291
                //
 
292
                int l_elen = pgStream.ReceiveInteger4();
 
293
                if (l_elen > 30000)
 
294
                {
 
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();
 
298
                }
 
299
 
 
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);
 
304
 
 
305
            case 'R':
 
306
                // Authentication request.
 
307
                // Get the message length
 
308
                int l_msgLen = pgStream.ReceiveInteger4();
 
309
 
 
310
                // Get the type of request
 
311
                int areq = pgStream.ReceiveInteger4();
 
312
 
 
313
                // Process the request.
 
314
                switch (areq)
 
315
                {
 
316
                case AUTH_REQ_CRYPT:
 
317
                    {
 
318
                        byte[] salt = pgStream.Receive(2);
 
319
 
 
320
                        if (logger.logDebug())
 
321
                            logger.debug(" <=BE AuthenticationReqCrypt(salt='" + new String(salt, "US-ASCII") + "')");
 
322
 
 
323
                        if (password == null)
 
324
                            throw new PSQLException(GT.tr("The server requested password-based authentication, but no password was provided."), PSQLState.CONNECTION_REJECTED);
 
325
 
 
326
                        byte[] encodedResult = UnixCrypt.crypt(salt, password.getBytes("UTF-8"));
 
327
 
 
328
                        if (logger.logDebug())
 
329
                            logger.debug(" FE=> Password(crypt='" + new String(encodedResult, "US-ASCII") + "')");
 
330
 
 
331
                        pgStream.SendChar('p');
 
332
                        pgStream.SendInteger4(4 + encodedResult.length + 1);
 
333
                        pgStream.Send(encodedResult);
 
334
                        pgStream.SendChar(0);
 
335
                        pgStream.flush();
 
336
 
 
337
                        break;
 
338
                    }
 
339
 
 
340
                case AUTH_REQ_MD5:
 
341
                    {
 
342
                        byte[] md5Salt = pgStream.Receive(4);
 
343
                        if (logger.logDebug())
 
344
                        {
 
345
                            logger.debug(" <=BE AuthenticationReqMD5(salt=" + Utils.toHexString(md5Salt) + ")");
 
346
                        }
 
347
 
 
348
                        if (password == null)
 
349
                            throw new PSQLException(GT.tr("The server requested password-based authentication, but no password was provided."), PSQLState.CONNECTION_REJECTED);
 
350
 
 
351
                        byte[] digest = MD5Digest.encode(user.getBytes("UTF-8"), password.getBytes("UTF-8"), md5Salt);
 
352
 
 
353
                        if (logger.logDebug())
 
354
                        {
 
355
                            logger.debug(" FE=> Password(md5digest=" + new String(digest, "US-ASCII") + ")");
 
356
                        }
 
357
 
 
358
                        pgStream.SendChar('p');
 
359
                        pgStream.SendInteger4(4 + digest.length + 1);
 
360
                        pgStream.Send(digest);
 
361
                        pgStream.SendChar(0);
 
362
                        pgStream.flush();
 
363
 
 
364
                        break;
 
365
                    }
 
366
 
 
367
                case AUTH_REQ_PASSWORD:
 
368
                    {
 
369
                        if (logger.logDebug())
 
370
                        {
 
371
                            logger.debug(" <=BE AuthenticationReqPassword");
 
372
                            logger.debug(" FE=> Password(password=<not shown>)");
 
373
                        }
 
374
 
 
375
                        if (password == null)
 
376
                            throw new PSQLException(GT.tr("The server requested password-based authentication, but no password was provided."), PSQLState.CONNECTION_REJECTED);
 
377
 
 
378
                        byte[] encodedPassword = password.getBytes("UTF-8");
 
379
 
 
380
                        pgStream.SendChar('p');
 
381
                        pgStream.SendInteger4(4 + encodedPassword.length + 1);
 
382
                        pgStream.Send(encodedPassword);
 
383
                        pgStream.SendChar(0);
 
384
                        pgStream.flush();
 
385
 
 
386
                        break;
 
387
                    }
 
388
 
 
389
                case AUTH_REQ_SSPI:
 
390
                    if (logger.logDebug())
 
391
                        logger.debug(" <=BE AuthenticationReqSSPI");
 
392
 
 
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);
 
394
 
 
395
                case AUTH_REQ_OK:
 
396
                    if (logger.logDebug())
 
397
                        logger.debug(" <=BE AuthenticationOk");
 
398
 
 
399
                    return ; // We're done.
 
400
 
 
401
                default:
 
402
                    if (logger.logDebug())
 
403
                        logger.debug(" <=BE AuthenticationReq (unsupported type " + ((int)areq) + ")");
 
404
 
 
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);
 
406
                }
 
407
 
 
408
                break;
 
409
 
 
410
            default:
 
411
                throw new PSQLException(GT.tr("Protocol error.  Session setup failed."), PSQLState.CONNECTION_UNABLE_TO_CONNECT);
 
412
            }
 
413
        }
 
414
    }
 
415
 
 
416
    private void readStartupMessages(PGStream pgStream, ProtocolConnectionImpl protoConnection, Logger logger) throws IOException, SQLException {
 
417
        while (true)
 
418
        {
 
419
            int beresp = pgStream.ReceiveChar();
 
420
            switch (beresp)
 
421
            {
 
422
            case 'Z':
 
423
                // Ready For Query; we're done.
 
424
                if (pgStream.ReceiveInteger4() != 5)
 
425
                    throw new IOException("unexpected length of ReadyForQuery packet");
 
426
 
 
427
                char tStatus = (char)pgStream.ReceiveChar();
 
428
                if (logger.logDebug())
 
429
                    logger.debug(" <=BE ReadyForQuery(" + tStatus + ")");
 
430
 
 
431
                // Update connection state.
 
432
                switch (tStatus)
 
433
                {
 
434
                case 'I':
 
435
                    protoConnection.setTransactionState(ProtocolConnection.TRANSACTION_IDLE);
 
436
                    break;
 
437
                case 'T':
 
438
                    protoConnection.setTransactionState(ProtocolConnection.TRANSACTION_OPEN);
 
439
                    break;
 
440
                case 'E':
 
441
                    protoConnection.setTransactionState(ProtocolConnection.TRANSACTION_FAILED);
 
442
                    break;
 
443
                default:
 
444
                    // Huh?
 
445
                    break;
 
446
                }
 
447
 
 
448
                return ;
 
449
 
 
450
            case 'K':
 
451
                // BackendKeyData
 
452
                int l_msgLen = pgStream.ReceiveInteger4();
 
453
                if (l_msgLen != 12)
 
454
                    throw new PSQLException(GT.tr("Protocol error.  Session setup failed."), PSQLState.CONNECTION_UNABLE_TO_CONNECT);
 
455
 
 
456
                int pid = pgStream.ReceiveInteger4();
 
457
                int ckey = pgStream.ReceiveInteger4();
 
458
 
 
459
                if (logger.logDebug())
 
460
                    logger.debug(" <=BE BackendKeyData(pid=" + pid + ",ckey=" + ckey + ")");
 
461
 
 
462
                protoConnection.setBackendKeyData(pid, ckey);
 
463
                break;
 
464
 
 
465
            case 'E':
 
466
                // Error
 
467
                int l_elen = pgStream.ReceiveInteger4();
 
468
                ServerErrorMessage l_errorMsg = new ServerErrorMessage(pgStream.ReceiveString(l_elen - 4), logger.getLogLevel());
 
469
 
 
470
                if (logger.logDebug())
 
471
                    logger.debug(" <=BE ErrorMessage(" + l_errorMsg + ")");
 
472
 
 
473
                throw new PSQLException(l_errorMsg);
 
474
 
 
475
            case 'N':
 
476
                // Warning
 
477
                int l_nlen = pgStream.ReceiveInteger4();
 
478
                ServerErrorMessage l_warnMsg = new ServerErrorMessage(pgStream.ReceiveString(l_nlen - 4), logger.getLogLevel());
 
479
 
 
480
                if (logger.logDebug())
 
481
                    logger.debug(" <=BE NoticeResponse(" + l_warnMsg + ")");
 
482
 
 
483
                protoConnection.addWarning(new PSQLWarning(l_warnMsg));
 
484
                break;
 
485
 
 
486
            case 'S':
 
487
                // ParameterStatus
 
488
                int l_len = pgStream.ReceiveInteger4();
 
489
                String name = pgStream.ReceiveString();
 
490
                String value = pgStream.ReceiveString();
 
491
 
 
492
                if (logger.logDebug())
 
493
                    logger.debug(" <=BE ParameterStatus(" + name + " = " + value + ")");
 
494
 
 
495
                if (name.equals("server_version"))
 
496
                    protoConnection.setServerVersion(value);
 
497
                else if (name.equals("client_encoding"))
 
498
                {
 
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"));
 
502
                }
 
503
                else if (name.equals("standard_conforming_strings"))
 
504
                {
 
505
                    if (value.equals("on"))
 
506
                        protoConnection.setStandardConformingStrings(true);
 
507
                    else if (value.equals("off"))
 
508
                        protoConnection.setStandardConformingStrings(false);
 
509
                    else
 
510
                        throw new PSQLException(GT.tr("Protocol error.  Session setup failed."), PSQLState.CONNECTION_UNABLE_TO_CONNECT);
 
511
                }
 
512
 
 
513
                break;
 
514
 
 
515
            default:
 
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);
 
519
            }
 
520
        }
 
521
    }
 
522
}