001: /*-------------------------------------------------------------------------
002: *
003: * Copyright (c) 2003-2005, PostgreSQL Global Development Group
004: * Copyright (c) 2004, Open Cloud Limited.
005: *
006: * IDENTIFICATION
007: * $PostgreSQL: pgjdbc/org/postgresql/core/v3/ConnectionFactoryImpl.java,v 1.13 2007/02/28 06:11:00 jurka Exp $
008: *
009: *-------------------------------------------------------------------------
010: */
011: package org.postgresql.core.v3;
012:
013: import java.util.Properties;
014:
015: import java.sql.*;
016: import java.io.IOException;
017: import java.net.ConnectException;
018:
019: import org.postgresql.Driver;
020: import org.postgresql.core.*;
021: import org.postgresql.util.PSQLException;
022: import org.postgresql.util.PSQLState;
023: import org.postgresql.util.PSQLWarning;
024: import org.postgresql.util.ServerErrorMessage;
025: import org.postgresql.util.UnixCrypt;
026: import org.postgresql.util.MD5Digest;
027: import org.postgresql.util.GT;
028:
029: /**
030: * ConnectionFactory implementation for version 3 (7.4+) connections.
031: *
032: * @author Oliver Jowett (oliver@opencloud.com), based on the previous implementation
033: */
034: public class ConnectionFactoryImpl extends ConnectionFactory {
035: private static final int AUTH_REQ_OK = 0;
036: private static final int AUTH_REQ_KRB4 = 1;
037: private static final int AUTH_REQ_KRB5 = 2;
038: private static final int AUTH_REQ_PASSWORD = 3;
039: private static final int AUTH_REQ_CRYPT = 4;
040: private static final int AUTH_REQ_MD5 = 5;
041: private static final int AUTH_REQ_SCM = 6;
042:
043: /** Marker exception; thrown when we want to fall back to using V2. */
044: private static class UnsupportedProtocolException extends
045: IOException {
046: }
047:
048: public ProtocolConnection openConnectionImpl(String host, int port,
049: String user, String database, Properties info, Logger logger)
050: throws SQLException {
051: // Extract interesting values from the info properties:
052: // - the SSL setting
053: boolean requireSSL = (info.getProperty("ssl") != null);
054: boolean trySSL = requireSSL; // XXX temporary until we revisit the ssl property values
055:
056: // NOTE: To simplify this code, it is assumed that if we are
057: // using the V3 protocol, then the database is at least 7.4. That
058: // eliminates the need to check database versions and maintain
059: // backward-compatible code here.
060: //
061: // Change by Chris Smith <cdsmith@twu.net>
062:
063: if (logger.logDebug())
064: logger
065: .debug("Trying to establish a protocol version 3 connection to "
066: + host + ":" + port);
067:
068: if (!Driver.sslEnabled()) {
069: if (requireSSL)
070: throw new PSQLException(GT
071: .tr("The driver does not support SSL."),
072: PSQLState.CONNECTION_FAILURE);
073: trySSL = false;
074: }
075:
076: //
077: // Establish a connection.
078: //
079:
080: PGStream newStream = null;
081: try {
082: newStream = new PGStream(host, port);
083:
084: // Construct and send an ssl startup packet if requested.
085: if (trySSL)
086: newStream = enableSSL(newStream, requireSSL, info,
087: logger);
088:
089: // Construct and send a startup packet.
090: String[][] params = { { "user", user },
091: { "database", database },
092: { "client_encoding", "UNICODE" },
093: { "DateStyle", "ISO" } };
094:
095: sendStartupPacket(newStream, params, logger);
096:
097: // Do authentication (until AuthenticationOk).
098: doAuthentication(newStream, user, info
099: .getProperty("password"), logger);
100:
101: // Do final startup.
102: ProtocolConnectionImpl protoConnection = new ProtocolConnectionImpl(
103: newStream, user, database, info, logger);
104: readStartupMessages(newStream, protoConnection, logger);
105:
106: // And we're done.
107: return protoConnection;
108: } catch (UnsupportedProtocolException upe) {
109: // Swallow this and return null so ConnectionFactory tries the next protocol.
110: if (logger.logDebug())
111: logger
112: .debug("Protocol not supported, abandoning connection.");
113: try {
114: newStream.close();
115: } catch (IOException e) {
116: }
117: return null;
118: } catch (ConnectException cex) {
119: // Added by Peter Mount <peter@retep.org.uk>
120: // ConnectException is thrown when the connection cannot be made.
121: // we trap this an return a more meaningful message for the end user
122: throw new PSQLException(
123: GT
124: .tr("Connection refused. Check that the hostname and port are correct and that the postmaster is accepting TCP/IP connections."),
125: PSQLState.CONNECTION_REJECTED, cex);
126: } catch (IOException ioe) {
127: if (newStream != null) {
128: try {
129: newStream.close();
130: } catch (IOException e) {
131: }
132: }
133: throw new PSQLException(GT
134: .tr("The connection attempt failed."),
135: PSQLState.CONNECTION_UNABLE_TO_CONNECT, ioe);
136: } catch (SQLException se) {
137: if (newStream != null) {
138: try {
139: newStream.close();
140: } catch (IOException e) {
141: }
142: }
143: throw se;
144: }
145: }
146:
147: private PGStream enableSSL(PGStream pgStream, boolean requireSSL,
148: Properties info, Logger logger) throws IOException,
149: SQLException {
150: if (logger.logDebug())
151: logger.debug(" FE=> SSLRequest");
152:
153: // Send SSL request packet
154: pgStream.SendInteger4(8);
155: pgStream.SendInteger2(1234);
156: pgStream.SendInteger2(5679);
157: pgStream.flush();
158:
159: // Now get the response from the backend, one of N, E, S.
160: int beresp = pgStream.ReceiveChar();
161: switch (beresp) {
162: case 'E':
163: if (logger.logDebug())
164: logger.debug(" <=BE SSLError");
165:
166: // Server doesn't even know about the SSL handshake protocol
167: if (requireSSL)
168: throw new PSQLException(GT
169: .tr("The server does not support SSL."),
170: PSQLState.CONNECTION_FAILURE);
171:
172: // We have to reconnect to continue.
173: pgStream.close();
174: return new PGStream(pgStream.getHost(), pgStream.getPort());
175:
176: case 'N':
177: if (logger.logDebug())
178: logger.debug(" <=BE SSLRefused");
179:
180: // Server does not support ssl
181: if (requireSSL)
182: throw new PSQLException(GT
183: .tr("The server does not support SSL."),
184: PSQLState.CONNECTION_FAILURE);
185:
186: return pgStream;
187:
188: case 'S':
189: if (logger.logDebug())
190: logger.debug(" <=BE SSLOk");
191:
192: // Server supports ssl
193: Driver.makeSSL(pgStream, info, logger);
194: return pgStream;
195:
196: default:
197: throw new PSQLException(
198: GT
199: .tr("An error occured while setting up the SSL connection."),
200: PSQLState.CONNECTION_FAILURE);
201: }
202: }
203:
204: private void sendStartupPacket(PGStream pgStream,
205: String[][] params, Logger logger) throws IOException {
206: if (logger.logDebug()) {
207: String details = "";
208: for (int i = 0; i < params.length; ++i) {
209: if (i != 0)
210: details += ", ";
211: details += params[i][0] + "=" + params[i][1];
212: }
213: logger.debug(" FE=> StartupPacket(" + details + ")");
214: }
215:
216: /*
217: * Precalculate message length and encode params.
218: */
219: int length = 4 + 4;
220: byte[][] encodedParams = new byte[params.length * 2][];
221: for (int i = 0; i < params.length; ++i) {
222: encodedParams[i * 2] = params[i][0].getBytes("US-ASCII");
223: encodedParams[i * 2 + 1] = params[i][1]
224: .getBytes("US-ASCII");
225: length += encodedParams[i * 2].length + 1
226: + encodedParams[i * 2 + 1].length + 1;
227: }
228:
229: length += 1; // Terminating \0
230:
231: /*
232: * Send the startup message.
233: */
234: pgStream.SendInteger4(length);
235: pgStream.SendInteger2(3); // protocol major
236: pgStream.SendInteger2(0); // protocol minor
237: for (int i = 0; i < encodedParams.length; ++i) {
238: pgStream.Send(encodedParams[i]);
239: pgStream.SendChar(0);
240: }
241:
242: pgStream.SendChar(0);
243: pgStream.flush();
244: }
245:
246: private void doAuthentication(PGStream pgStream, String user,
247: String password, Logger logger) throws IOException,
248: SQLException {
249: // Now get the response from the backend, either an error message
250: // or an authentication request
251:
252: while (true) {
253: int beresp = pgStream.ReceiveChar();
254:
255: switch (beresp) {
256: case 'E':
257: // An error occured, so pass the error message to the
258: // user.
259: //
260: // The most common one to be thrown here is:
261: // "User authentication failed"
262: //
263: int l_elen = pgStream.ReceiveInteger4();
264: if (l_elen > 30000) {
265: // if the error length is > than 30000 we assume this is really a v2 protocol
266: // server, so trigger fallback.
267: throw new UnsupportedProtocolException();
268: }
269:
270: ServerErrorMessage errorMsg = new ServerErrorMessage(
271: pgStream.ReceiveString(l_elen - 4), logger
272: .getLogLevel());
273: if (logger.logDebug())
274: logger
275: .debug(" <=BE ErrorMessage(" + errorMsg
276: + ")");
277: throw new PSQLException(errorMsg);
278:
279: case 'R':
280: // Authentication request.
281: // Get the message length
282: int l_msgLen = pgStream.ReceiveInteger4();
283:
284: // Get the type of request
285: int areq = pgStream.ReceiveInteger4();
286:
287: // Process the request.
288: switch (areq) {
289: case AUTH_REQ_CRYPT: {
290: byte[] rst = new byte[2];
291: rst[0] = (byte) pgStream.ReceiveChar();
292: rst[1] = (byte) pgStream.ReceiveChar();
293: String salt = new String(rst, 0, 2, "US-ASCII");
294:
295: if (logger.logDebug())
296: logger
297: .debug(" <=BE AuthenticationReqCrypt(salt='"
298: + salt + "')");
299:
300: if (password == null)
301: throw new PSQLException(
302: GT
303: .tr("The server requested password-based authentication, but no password was provided."),
304: PSQLState.CONNECTION_REJECTED);
305:
306: String result = UnixCrypt.crypt(salt, password);
307: byte[] encodedResult = result.getBytes("US-ASCII");
308:
309: if (logger.logDebug())
310: logger.debug(" FE=> Password(crypt='" + result
311: + "')");
312:
313: pgStream.SendChar('p');
314: pgStream.SendInteger4(4 + encodedResult.length + 1);
315: pgStream.Send(encodedResult);
316: pgStream.SendChar(0);
317: pgStream.flush();
318:
319: break;
320: }
321:
322: case AUTH_REQ_MD5: {
323: byte[] md5Salt = pgStream.Receive(4);
324: if (logger.logDebug()) {
325: logger.debug(" <=BE AuthenticationReqMD5(salt="
326: + Utils.toHexString(md5Salt) + ")");
327: }
328:
329: if (password == null)
330: throw new PSQLException(
331: GT
332: .tr("The server requested password-based authentication, but no password was provided."),
333: PSQLState.CONNECTION_REJECTED);
334:
335: byte[] digest = MD5Digest.encode(user, password,
336: md5Salt);
337:
338: if (logger.logDebug()) {
339: logger.debug(" FE=> Password(md5digest="
340: + new String(digest, "US-ASCII") + ")");
341: }
342:
343: pgStream.SendChar('p');
344: pgStream.SendInteger4(4 + digest.length + 1);
345: pgStream.Send(digest);
346: pgStream.SendChar(0);
347: pgStream.flush();
348:
349: break;
350: }
351:
352: case AUTH_REQ_PASSWORD: {
353: if (logger.logDebug()) {
354: logger.debug(" <=BE AuthenticationReqPassword");
355: logger
356: .debug(" FE=> Password(password=<not shown>)");
357: }
358:
359: if (password == null)
360: throw new PSQLException(
361: GT
362: .tr("The server requested password-based authentication, but no password was provided."),
363: PSQLState.CONNECTION_REJECTED);
364:
365: byte[] encodedPassword = password
366: .getBytes("US-ASCII");
367:
368: pgStream.SendChar('p');
369: pgStream
370: .SendInteger4(4 + encodedPassword.length + 1);
371: pgStream.Send(encodedPassword);
372: pgStream.SendChar(0);
373: pgStream.flush();
374:
375: break;
376: }
377:
378: case AUTH_REQ_OK:
379: if (logger.logDebug())
380: logger.debug(" <=BE AuthenticationOk");
381:
382: return; // We're done.
383:
384: default:
385: if (logger.logDebug())
386: logger
387: .debug(" <=BE AuthenticationReq (unsupported type "
388: + ((int) areq) + ")");
389:
390: throw new PSQLException(
391: GT
392: .tr(
393: "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.",
394: new Integer(areq)),
395: PSQLState.CONNECTION_REJECTED);
396: }
397:
398: break;
399:
400: default:
401: throw new PSQLException(GT
402: .tr("Protocol error. Session setup failed."),
403: PSQLState.CONNECTION_UNABLE_TO_CONNECT);
404: }
405: }
406: }
407:
408: private void readStartupMessages(PGStream pgStream,
409: ProtocolConnectionImpl protoConnection, Logger logger)
410: throws IOException, SQLException {
411: while (true) {
412: int beresp = pgStream.ReceiveChar();
413: switch (beresp) {
414: case 'Z':
415: // Ready For Query; we're done.
416: if (pgStream.ReceiveInteger4() != 5)
417: throw new IOException(
418: "unexpected length of ReadyForQuery packet");
419:
420: char tStatus = (char) pgStream.ReceiveChar();
421: if (logger.logDebug())
422: logger
423: .debug(" <=BE ReadyForQuery(" + tStatus
424: + ")");
425:
426: // Update connection state.
427: switch (tStatus) {
428: case 'I':
429: protoConnection
430: .setTransactionState(ProtocolConnection.TRANSACTION_IDLE);
431: break;
432: case 'T':
433: protoConnection
434: .setTransactionState(ProtocolConnection.TRANSACTION_OPEN);
435: break;
436: case 'E':
437: protoConnection
438: .setTransactionState(ProtocolConnection.TRANSACTION_FAILED);
439: break;
440: default:
441: // Huh?
442: break;
443: }
444:
445: return;
446:
447: case 'K':
448: // BackendKeyData
449: int l_msgLen = pgStream.ReceiveInteger4();
450: if (l_msgLen != 12)
451: throw new PSQLException(
452: GT
453: .tr("Protocol error. Session setup failed."),
454: 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
461: + ",ckey=" + ckey + ")");
462:
463: protoConnection.setBackendKeyData(pid, ckey);
464: break;
465:
466: case 'E':
467: // Error
468: int l_elen = pgStream.ReceiveInteger4();
469: ServerErrorMessage l_errorMsg = new ServerErrorMessage(
470: pgStream.ReceiveString(l_elen - 4), logger
471: .getLogLevel());
472:
473: if (logger.logDebug())
474: logger.debug(" <=BE ErrorMessage(" + l_errorMsg
475: + ")");
476:
477: throw new PSQLException(l_errorMsg);
478:
479: case 'N':
480: // Warning
481: int l_nlen = pgStream.ReceiveInteger4();
482: ServerErrorMessage l_warnMsg = new ServerErrorMessage(
483: pgStream.ReceiveString(l_nlen - 4), logger
484: .getLogLevel());
485:
486: if (logger.logDebug())
487: logger.debug(" <=BE NoticeResponse(" + l_warnMsg
488: + ")");
489:
490: protoConnection.addWarning(new PSQLWarning(l_warnMsg));
491: break;
492:
493: case 'S':
494: // ParameterStatus
495: int l_len = pgStream.ReceiveInteger4();
496: String name = pgStream.ReceiveString();
497: String value = pgStream.ReceiveString();
498:
499: if (logger.logDebug())
500: logger.debug(" <=BE ParameterStatus(" + name
501: + " = " + value + ")");
502:
503: if (name.equals("server_version"))
504: protoConnection.setServerVersion(value);
505: else if (name.equals("client_encoding")) {
506: if (!value.equals("UNICODE"))
507: throw new PSQLException(
508: GT
509: .tr("Protocol error. Session setup failed."),
510: PSQLState.CONNECTION_UNABLE_TO_CONNECT);
511: pgStream.setEncoding(Encoding
512: .getDatabaseEncoding("UNICODE"));
513: } else if (name.equals("standard_conforming_strings")) {
514: if (value.equals("on"))
515: protoConnection
516: .setStandardConformingStrings(true);
517: else if (value.equals("off"))
518: protoConnection
519: .setStandardConformingStrings(false);
520: else
521: throw new PSQLException(
522: GT
523: .tr("Protocol error. Session setup failed."),
524: PSQLState.CONNECTION_UNABLE_TO_CONNECT);
525: }
526:
527: break;
528:
529: default:
530: if (logger.logDebug())
531: logger.debug("invalid message type="
532: + (char) beresp);
533: throw new PSQLException(GT
534: .tr("Protocol error. Session setup failed."),
535: PSQLState.CONNECTION_UNABLE_TO_CONNECT);
536: }
537: }
538: }
539: }
|