0001: // jTDS JDBC Driver for Microsoft SQL Server and Sybase
0002: // Copyright (C) 2004 The jTDS Project
0003: //
0004: // This library is free software; you can redistribute it and/or
0005: // modify it under the terms of the GNU Lesser General Public
0006: // License as published by the Free Software Foundation; either
0007: // version 2.1 of the License, or (at your option) any later version.
0008: //
0009: // This library is distributed in the hope that it will be useful,
0010: // but WITHOUT ANY WARRANTY; without even the implied warranty of
0011: // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0012: // Lesser General Public License for more details.
0013: //
0014: // You should have received a copy of the GNU Lesser General Public
0015: // License along with this library; if not, write to the Free Software
0016: // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
0017: //
0018: package net.sourceforge.jtds.jdbc;
0019:
0020: import java.io.*;
0021: import java.sql.*;
0022: import java.util.Arrays;
0023: import java.util.ArrayList;
0024: import java.util.HashMap;
0025: import java.util.Random;
0026:
0027: import net.sourceforge.jtds.ssl.*;
0028: import net.sourceforge.jtds.util.*;
0029:
0030: /**
0031: * This class implements the Sybase / Microsoft TDS protocol.
0032: * <p>
0033: * Implementation notes:
0034: * <ol>
0035: * <li>This class, together with TdsData, encapsulates all of the TDS specific logic
0036: * required by the driver.
0037: * <li>This is a ground up reimplementation of the TDS protocol and is rather
0038: * simpler, and hopefully easier to understand, than the original.
0039: * <li>The layout of the various Login packets is derived from the original code
0040: * and freeTds work, and incorporates changes including the ability to login as a TDS 5.0 user.
0041: * <li>All network I/O errors are trapped here, reported to the log (if active)
0042: * and the parent Connection object is notified that the connection should be considered
0043: * closed.
0044: * <li>Rather than having a large number of classes one for each token, useful information
0045: * about the current token is gathered together in the inner TdsToken class.
0046: * <li>As the rest of the driver interfaces to this code via higher-level method calls there
0047: * should be know need for knowledge of the TDS protocol to leak out of this class.
0048: * It is for this reason that all the TDS Token constants are private.
0049: * </ol>
0050: *
0051: * @author Mike Hutchinson
0052: * @author Matt Brinkley
0053: * @author Alin Sinpalean
0054: * @author FreeTDS project
0055: * @version $Id: TdsCore.java,v 1.115 2007/07/08 17:28:23 bheineman Exp $
0056: */
0057: public class TdsCore {
0058: /**
0059: * Inner static class used to hold information about TDS tokens read.
0060: */
0061: private static class TdsToken {
0062: /** The current TDS token byte. */
0063: byte token;
0064: /** The status field from a DONE packet. */
0065: byte status;
0066: /** The operation field from a DONE packet. */
0067: byte operation;
0068: /** The update count from a DONE packet. */
0069: int updateCount;
0070: /** The nonce from an NTLM challenge packet. */
0071: byte[] nonce;
0072: /** NTLM authentication message. */
0073: byte[] ntlmMessage;
0074: /** target info for NTLM message TODO: I don't need to store these!!! */
0075: byte[] ntlmTarget;
0076: /** The dynamic parameters from the last TDS_DYNAMIC token. */
0077: ColInfo[] dynamParamInfo;
0078: /** The dynamic parameter data from the last TDS_DYNAMIC token. */
0079: Object[] dynamParamData;
0080:
0081: /**
0082: * Retrieve the update count status.
0083: *
0084: * @return <code>boolean</code> true if the update count is valid.
0085: */
0086: boolean isUpdateCount() {
0087: return (token == TDS_DONE_TOKEN || token == TDS_DONEINPROC_TOKEN)
0088: && (status & DONE_ROW_COUNT) != 0;
0089: }
0090:
0091: /**
0092: * Retrieve the DONE token status.
0093: *
0094: * @return <code>boolean</code> true if the current token is a DONE packet.
0095: */
0096: boolean isEndToken() {
0097: return token == TDS_DONE_TOKEN
0098: || token == TDS_DONEINPROC_TOKEN
0099: || token == TDS_DONEPROC_TOKEN;
0100: }
0101:
0102: /**
0103: * Retrieve the NTLM challenge status.
0104: *
0105: * @return <code>boolean</code> true if the current token is an NTLM challenge.
0106: */
0107: boolean isAuthToken() {
0108: return token == TDS_AUTH_TOKEN;
0109: }
0110:
0111: /**
0112: * Retrieve the results pending status.
0113: *
0114: * @return <code>boolean</code> true if more results in input.
0115: */
0116: boolean resultsPending() {
0117: return !isEndToken() || ((status & DONE_MORE_RESULTS) != 0);
0118: }
0119:
0120: /**
0121: * Retrieve the result set status.
0122: *
0123: * @return <code>boolean</code> true if the current token is a result set.
0124: */
0125: boolean isResultSet() {
0126: return token == TDS_COLFMT_TOKEN
0127: || token == TDS7_RESULT_TOKEN
0128: || token == TDS_RESULT_TOKEN
0129: || token == TDS5_WIDE_RESULT
0130: || token == TDS_COLINFO_TOKEN
0131: || token == TDS_ROW_TOKEN;
0132: }
0133:
0134: /**
0135: * Retrieve the row data status.
0136: *
0137: * @return <code>boolean</code> true if the current token is a result row.
0138: */
0139: public boolean isRowData() {
0140: return token == TDS_ROW_TOKEN;
0141: }
0142:
0143: }
0144:
0145: /**
0146: * Inner static class used to hold table meta data.
0147: */
0148: private static class TableMetaData {
0149: /** Table catalog (database) name. */
0150: String catalog;
0151: /** Table schema (user) name. */
0152: String schema;
0153: /** Table name. */
0154: String name;
0155: }
0156:
0157: //
0158: // Package private constants
0159: //
0160: /** Minimum network packet size. */
0161: public static final int MIN_PKT_SIZE = 512;
0162: /** Default minimum network packet size for TDS 7.0 and newer. */
0163: public static final int DEFAULT_MIN_PKT_SIZE_TDS70 = 4096;
0164: /** Maximum network packet size. */
0165: public static final int MAX_PKT_SIZE = 32768;
0166: /** The size of the packet header. */
0167: public static final int PKT_HDR_LEN = 8;
0168: /** TDS 4.2 or 7.0 Query packet. */
0169: public static final byte QUERY_PKT = 1;
0170: /** TDS 4.2 or 5.0 Login packet. */
0171: public static final byte LOGIN_PKT = 2;
0172: /** TDS Remote Procedure Call. */
0173: public static final byte RPC_PKT = 3;
0174: /** TDS Reply packet. */
0175: public static final byte REPLY_PKT = 4;
0176: /** TDS Cancel packet. */
0177: public static final byte CANCEL_PKT = 6;
0178: /** TDS MSDTC packet. */
0179: public static final byte MSDTC_PKT = 14;
0180: /** TDS 5.0 Query packet. */
0181: public static final byte SYBQUERY_PKT = 15;
0182: /** TDS 7.0 Login packet. */
0183: public static final byte MSLOGIN_PKT = 16;
0184: /** TDS 7.0 NTLM Authentication packet. */
0185: public static final byte NTLMAUTH_PKT = 17;
0186: /** SQL 2000 prelogin negotiation packet. */
0187: public static final byte PRELOGIN_PKT = 18;
0188: /** SSL Mode - Login packet must be encrypted. */
0189: public static final int SSL_ENCRYPT_LOGIN = 0;
0190: /** SSL Mode - Client requested force encryption. */
0191: public static final int SSL_CLIENT_FORCE_ENCRYPT = 1;
0192: /** SSL Mode - No server certificate installed. */
0193: public static final int SSL_NO_ENCRYPT = 2;
0194: /** SSL Mode - Server requested force encryption. */
0195: public static final int SSL_SERVER_FORCE_ENCRYPT = 3;
0196:
0197: //
0198: // Sub packet types
0199: //
0200: /** TDS 5.0 Parameter format token. */
0201: private static final byte TDS5_PARAMFMT2_TOKEN = (byte) 32; // 0x20
0202: /** TDS 5.0 Language token. */
0203: private static final byte TDS_LANG_TOKEN = (byte) 33; // 0x21
0204: /** TSD 5.0 Wide result set token. */
0205: private static final byte TDS5_WIDE_RESULT = (byte) 97; // 0x61
0206: /** TDS 5.0 Close token. */
0207: private static final byte TDS_CLOSE_TOKEN = (byte) 113; // 0x71
0208: /** TDS DBLIB Offsets token. */
0209: private static final byte TDS_OFFSETS_TOKEN = (byte) 120; // 0x78
0210: /** TDS Procedure call return status token. */
0211: private static final byte TDS_RETURNSTATUS_TOKEN = (byte) 121; // 0x79
0212: /** TDS Procedure ID token. */
0213: private static final byte TDS_PROCID = (byte) 124; // 0x7C
0214: /** TDS 7.0 Result set column meta data token. */
0215: private static final byte TDS7_RESULT_TOKEN = (byte) 129; // 0x81
0216: /** TDS 7.0 Computed Result set column meta data token. */
0217: private static final byte TDS7_COMP_RESULT_TOKEN = (byte) 136; // 0x88
0218: /** TDS 4.2 Column names token. */
0219: private static final byte TDS_COLNAME_TOKEN = (byte) 160; // 0xA0
0220: /** TDS 4.2 Column meta data token. */
0221: private static final byte TDS_COLFMT_TOKEN = (byte) 161; // 0xA1
0222: /** TDS Table name token. */
0223: private static final byte TDS_TABNAME_TOKEN = (byte) 164; // 0xA4
0224: /** TDS Cursor results column infomation token. */
0225: private static final byte TDS_COLINFO_TOKEN = (byte) 165; // 0xA5
0226: /** TDS Optional command token. */
0227: private static final byte TDS_OPTIONCMD_TOKEN = (byte) 166; // 0xA6
0228: /** TDS Computed result set names token. */
0229: private static final byte TDS_COMP_NAMES_TOKEN = (byte) 167; // 0xA7
0230: /** TDS Computed result set token. */
0231: private static final byte TDS_COMP_RESULT_TOKEN = (byte) 168; // 0xA8
0232: /** TDS Order by columns token. */
0233: private static final byte TDS_ORDER_TOKEN = (byte) 169; // 0xA9
0234: /** TDS error result token. */
0235: private static final byte TDS_ERROR_TOKEN = (byte) 170; // 0xAA
0236: /** TDS Information message token. */
0237: private static final byte TDS_INFO_TOKEN = (byte) 171; // 0xAB
0238: /** TDS Output parameter value token. */
0239: private static final byte TDS_PARAM_TOKEN = (byte) 172; // 0xAC
0240: /** TDS Login acknowledgement token. */
0241: private static final byte TDS_LOGINACK_TOKEN = (byte) 173; // 0xAD
0242: /** TDS control token. */
0243: private static final byte TDS_CONTROL_TOKEN = (byte) 174; // 0xAE
0244: /** TDS Result set data row token. */
0245: private static final byte TDS_ROW_TOKEN = (byte) 209; // 0xD1
0246: /** TDS Computed result set data row token. */
0247: private static final byte TDS_ALTROW = (byte) 211; // 0xD3
0248: /** TDS 5.0 parameter value token. */
0249: private static final byte TDS5_PARAMS_TOKEN = (byte) 215; // 0xD7
0250: /** TDS 5.0 capabilities token. */
0251: private static final byte TDS_CAP_TOKEN = (byte) 226; // 0xE2
0252: /** TDS environment change token. */
0253: private static final byte TDS_ENVCHANGE_TOKEN = (byte) 227; // 0xE3
0254: /** TDS 5.0 message token. */
0255: private static final byte TDS_MSG50_TOKEN = (byte) 229; // 0xE5
0256: /** TDS 5.0 RPC token. */
0257: private static final byte TDS_DBRPC_TOKEN = (byte) 230; // 0xE6
0258: /** TDS 5.0 Dynamic SQL token. */
0259: private static final byte TDS5_DYNAMIC_TOKEN = (byte) 231; // 0xE7
0260: /** TDS 5.0 parameter descriptor token. */
0261: private static final byte TDS5_PARAMFMT_TOKEN = (byte) 236; // 0xEC
0262: /** TDS 7.0 NTLM authentication challenge token. */
0263: private static final byte TDS_AUTH_TOKEN = (byte) 237; // 0xED
0264: /** TDS 5.0 Result set column meta data token. */
0265: private static final byte TDS_RESULT_TOKEN = (byte) 238; // 0xEE
0266: /** TDS done token. */
0267: private static final byte TDS_DONE_TOKEN = (byte) 253; // 0xFD DONE
0268: /** TDS done procedure token. */
0269: private static final byte TDS_DONEPROC_TOKEN = (byte) 254; // 0xFE DONEPROC
0270: /** TDS done in procedure token. */
0271: private static final byte TDS_DONEINPROC_TOKEN = (byte) 255; // 0xFF DONEINPROC
0272:
0273: //
0274: // Environment change payload codes
0275: //
0276: /** Environment change: database changed. */
0277: private static final byte TDS_ENV_DATABASE = (byte) 1;
0278: /** Environment change: language changed. */
0279: private static final byte TDS_ENV_LANG = (byte) 2;
0280: /** Environment change: charset changed. */
0281: private static final byte TDS_ENV_CHARSET = (byte) 3;
0282: /** Environment change: network packet size changed. */
0283: private static final byte TDS_ENV_PACKSIZE = (byte) 4;
0284: /** Environment change: locale changed. */
0285: private static final byte TDS_ENV_LCID = (byte) 5;
0286: /** Environment change: TDS 8 collation changed. */
0287: private static final byte TDS_ENV_SQLCOLLATION = (byte) 7; // TDS8 Collation
0288:
0289: //
0290: // Static variables used only for performance
0291: //
0292: /** Used to optimize the {@link #getParameters()} call */
0293: private static final ParamInfo[] EMPTY_PARAMETER_INFO = new ParamInfo[0];
0294:
0295: //
0296: // End token status bytes
0297: //
0298: /** Done: more results are expected. */
0299: private static final byte DONE_MORE_RESULTS = (byte) 0x01;
0300: /** Done: command caused an error. */
0301: private static final byte DONE_ERROR = (byte) 0x02;
0302: /** Done: There is a valid row count. */
0303: private static final byte DONE_ROW_COUNT = (byte) 0x10;
0304: /** Done: Cancel acknowledgement. */
0305: static final byte DONE_CANCEL = (byte) 0x20;
0306: /**
0307: * Done: Response terminator (if more than one request packet is sent, each
0308: * response is terminated by a DONE packet with this flag set).
0309: */
0310: private static final byte DONE_END_OF_RESPONSE = (byte) 0x80;
0311:
0312: //
0313: // Prepared SQL types
0314: //
0315: /** Do not prepare SQL */
0316: public static final int UNPREPARED = 0;
0317: /** Prepare SQL using temporary stored procedures */
0318: public static final int TEMPORARY_STORED_PROCEDURES = 1;
0319: /** Prepare SQL using sp_executesql */
0320: public static final int EXECUTE_SQL = 2;
0321: /** Prepare SQL using sp_prepare and sp_execute */
0322: public static final int PREPARE = 3;
0323:
0324: //
0325: // Sybase capability flags
0326: //
0327: /** Sybase char and binary > 255.*/
0328: static final int SYB_LONGDATA = 1;
0329: /** Sybase date and time data types.*/
0330: static final int SYB_DATETIME = 2;
0331: /** Sybase nullable bit type.*/
0332: static final int SYB_BITNULL = 4;
0333: /** Sybase extended column meta data.*/
0334: static final int SYB_EXTCOLINFO = 8;
0335: /** Sybase univarchar etc. */
0336: static final int SYB_UNICODE = 16;
0337: /** Sybase 15+ unitext. */
0338: static final int SYB_UNITEXT = 32;
0339: /** Sybase 15+ bigint. */
0340: static final int SYB_BIGINT = 64;
0341:
0342: /** Cancel has been generated by <code>Statement.cancel()</code>. */
0343: private final static int ASYNC_CANCEL = 0;
0344: /** Cancel has been generated by a query timeout. */
0345: private final static int TIMEOUT_CANCEL = 1;
0346:
0347: /** Map of system stored procedures that have shortcuts in TDS8. */
0348: private static HashMap tds8SpNames = new HashMap();
0349: static {
0350: tds8SpNames.put("sp_cursor", new Integer(1));
0351: tds8SpNames.put("sp_cursoropen", new Integer(2));
0352: tds8SpNames.put("sp_cursorprepare", new Integer(3));
0353: tds8SpNames.put("sp_cursorexecute", new Integer(4));
0354: tds8SpNames.put("sp_cursorprepexec", new Integer(5));
0355: tds8SpNames.put("sp_cursorunprepare", new Integer(6));
0356: tds8SpNames.put("sp_cursorfetch", new Integer(7));
0357: tds8SpNames.put("sp_cursoroption", new Integer(8));
0358: tds8SpNames.put("sp_cursorclose", new Integer(9));
0359: tds8SpNames.put("sp_executesql", new Integer(10));
0360: tds8SpNames.put("sp_prepare", new Integer(11));
0361: tds8SpNames.put("sp_execute", new Integer(12));
0362: tds8SpNames.put("sp_prepexec", new Integer(13));
0363: tds8SpNames.put("sp_prepexecrpc", new Integer(14));
0364: tds8SpNames.put("sp_unprepare", new Integer(15));
0365: }
0366:
0367: //
0368: // Class variables
0369: //
0370: /** Name of the client host (it can take quite a while to find it out if DNS is configured incorrectly). */
0371: private static String hostName;
0372: /** A reference to ntlm.SSPIJNIClient. */
0373: private static SSPIJNIClient sspiJNIClient;
0374:
0375: //
0376: // Instance variables
0377: //
0378: /** The Connection object that created this object. */
0379: private final ConnectionJDBC2 connection;
0380: /** The TDS version being supported by this connection. */
0381: private int tdsVersion;
0382: /** The make of SQL Server (Sybase/Microsoft). */
0383: private final int serverType;
0384: /** The Shared network socket object. */
0385: private final SharedSocket socket;
0386: /** The output server request stream. */
0387: private final RequestStream out;
0388: /** The input server response stream. */
0389: private final ResponseStream in;
0390: /** True if the server response is fully read. */
0391: private boolean endOfResponse = true;
0392: /** True if the current result set is at end of file. */
0393: private boolean endOfResults = true;
0394: /** The array of column meta data objects for this result set. */
0395: private ColInfo[] columns;
0396: /** The array of column data objects in the current row. */
0397: private Object[] rowData;
0398: /** The array of table names associated with this result. */
0399: private TableMetaData[] tables;
0400: /** The descriptor object for the current TDS token. */
0401: private TdsToken currentToken = new TdsToken();
0402: /** The stored procedure return status. */
0403: private Integer returnStatus;
0404: /** The return parameter meta data object for the current procedure call. */
0405: private ParamInfo returnParam;
0406: /** The array of parameter meta data objects for the current procedure call. */
0407: private ParamInfo[] parameters;
0408: /** The index of the next output parameter to populate. */
0409: private int nextParam = -1;
0410: /** The head of the diagnostic messages chain. */
0411: private final SQLDiagnostic messages;
0412: /** Indicates that this object is closed. */
0413: private boolean isClosed;
0414: /** Flag that indicates if logon() should try to use Windows Single Sign On using SSPI. */
0415: private boolean ntlmAuthSSO;
0416: /** Indicates that a fatal error has occured and the connection will close. */
0417: private boolean fatalError;
0418: /** Mutual exclusion lock on connection. */
0419: private Semaphore connectionLock;
0420: /** Indicates processing a batch. */
0421: private boolean inBatch;
0422: /** Indicates type of SSL connection. */
0423: private int sslMode = SSL_NO_ENCRYPT;
0424: /** Indicates pending cancel that needs to be cleared. */
0425: private boolean cancelPending;
0426: /** Synchronization monitor for {@link #cancelPending}. */
0427: private final int[] cancelMonitor = new int[1];
0428:
0429: /**
0430: * Construct a TdsCore object.
0431: *
0432: * @param connection The connection which owns this object.
0433: * @param messages The SQLDiagnostic messages chain.
0434: */
0435: TdsCore(ConnectionJDBC2 connection, SQLDiagnostic messages) {
0436: this .connection = connection;
0437: this .socket = connection.getSocket();
0438: this .messages = messages;
0439: serverType = connection.getServerType();
0440: tdsVersion = socket.getTdsVersion();
0441: out = socket.getRequestStream(connection.getNetPacketSize(),
0442: connection.getMaxPrecision());
0443: in = socket.getResponseStream(out, connection
0444: .getNetPacketSize());
0445: }
0446:
0447: /**
0448: * Check that the connection is still open.
0449: *
0450: * @throws SQLException
0451: */
0452: private void checkOpen() throws SQLException {
0453: if (connection.isClosed()) {
0454: throw new SQLException(Messages.get("error.generic.closed",
0455: "Connection"), "HY010");
0456: }
0457: }
0458:
0459: /**
0460: * Retrieve the TDS protocol version.
0461: *
0462: * @return The protocol version as an <code>int</code>.
0463: */
0464: int getTdsVersion() {
0465: return tdsVersion;
0466: }
0467:
0468: /**
0469: * Retrieve the current result set column descriptors.
0470: *
0471: * @return The column descriptors as a <code>ColInfo[]</code>.
0472: */
0473: ColInfo[] getColumns() {
0474: return columns;
0475: }
0476:
0477: /**
0478: * Sets the column meta data.
0479: *
0480: * @param columns the column descriptor array
0481: */
0482: void setColumns(ColInfo[] columns) {
0483: this .columns = columns;
0484: this .rowData = new Object[columns.length];
0485: this .tables = null;
0486: }
0487:
0488: /**
0489: * Retrieve the parameter meta data from a Sybase prepare.
0490: *
0491: * @return The parameter descriptors as a <code>ParamInfo[]</code>.
0492: */
0493: ParamInfo[] getParameters() {
0494: if (currentToken.dynamParamInfo != null) {
0495: ParamInfo[] params = new ParamInfo[currentToken.dynamParamInfo.length];
0496:
0497: for (int i = 0; i < params.length; i++) {
0498: ColInfo ci = currentToken.dynamParamInfo[i];
0499: params[i] = new ParamInfo(ci, ci.realName, null, 0);
0500: }
0501:
0502: return params;
0503: }
0504:
0505: return EMPTY_PARAMETER_INFO;
0506: }
0507:
0508: /**
0509: * Retrieve the current result set data items.
0510: *
0511: * @return the row data as an <code>Object</code> array
0512: */
0513: Object[] getRowData() {
0514: return rowData;
0515: }
0516:
0517: /**
0518: * Negotiate SSL settings with SQL 2000+ server.
0519: * <p/>
0520: * Server returns the following values for SSL mode:
0521: * <ol>
0522: * <ll>0 = Certificate installed encrypt login packet only.
0523: * <li>1 = Certificate installed client requests force encryption.
0524: * <li>2 = No certificate no encryption possible.
0525: * <li>3 = Server requests force encryption.
0526: * </ol>
0527: * @param instance The server instance name.
0528: * @param ssl The SSL URL property value.
0529: * @throws IOException
0530: */
0531: void negotiateSSL(String instance, String ssl) throws IOException,
0532: SQLException {
0533: if (!ssl.equalsIgnoreCase(Ssl.SSL_OFF)) {
0534: if (ssl.equalsIgnoreCase(Ssl.SSL_REQUIRE)
0535: || ssl.equalsIgnoreCase(Ssl.SSL_AUTHENTICATE)) {
0536: sendPreLoginPacket(instance, true);
0537: sslMode = readPreLoginPacket();
0538: if (sslMode != SSL_CLIENT_FORCE_ENCRYPT
0539: && sslMode != SSL_SERVER_FORCE_ENCRYPT) {
0540: throw new SQLException(Messages
0541: .get("error.ssl.encryptionoff"), "08S01");
0542: }
0543: } else {
0544: sendPreLoginPacket(instance, false);
0545: sslMode = readPreLoginPacket();
0546: }
0547: if (sslMode != SSL_NO_ENCRYPT) {
0548: socket.enableEncryption(ssl);
0549: }
0550: }
0551: }
0552:
0553: /**
0554: * Login to the SQL Server.
0555: *
0556: * @param serverName server host name
0557: * @param database required database
0558: * @param user user name
0559: * @param password user password
0560: * @param domain Windows NT domain (or null)
0561: * @param charset required server character set
0562: * @param appName application name
0563: * @param progName library name
0564: * @param wsid workstation ID
0565: * @param language language to use for server messages
0566: * @param macAddress client network MAC address
0567: * @param packetSize required network packet size
0568: * @throws SQLException if an error occurs
0569: */
0570: void login(final String serverName, final String database,
0571: final String user, final String password,
0572: final String domain, final String charset,
0573: final String appName, final String progName, String wsid,
0574: final String language, final String macAddress,
0575: final int packetSize) throws SQLException {
0576: try {
0577: if (wsid.length() == 0) {
0578: wsid = getHostName();
0579: }
0580: if (tdsVersion >= Driver.TDS70) {
0581: sendMSLoginPkt(serverName, database, user, password,
0582: domain, appName, progName, wsid, language,
0583: macAddress, packetSize);
0584: } else if (tdsVersion == Driver.TDS50) {
0585: send50LoginPkt(serverName, user, password, charset,
0586: appName, progName, wsid, language, packetSize);
0587: } else {
0588: send42LoginPkt(serverName, user, password, charset,
0589: appName, progName, wsid, language, packetSize);
0590: }
0591: if (sslMode == SSL_ENCRYPT_LOGIN) {
0592: socket.disableEncryption();
0593: }
0594: nextToken();
0595:
0596: while (!endOfResponse) {
0597: if (currentToken.isAuthToken()) {
0598: sendNtlmChallengeResponse(currentToken.nonce, user,
0599: password, domain);
0600: }
0601:
0602: nextToken();
0603: }
0604:
0605: messages.checkErrors();
0606: } catch (IOException ioe) {
0607: throw Support.linkException(
0608: new SQLException(Messages.get(
0609: "error.generic.ioerror", ioe.getMessage()),
0610: "08S01"), ioe);
0611: }
0612: }
0613:
0614: /**
0615: * Get the next result set or update count from the TDS stream.
0616: *
0617: * @return <code>boolean</code> if the next item is a result set.
0618: * @throws SQLException if an I/O or protocol error occurs; server errors
0619: * are queued up and not thrown
0620: */
0621: boolean getMoreResults() throws SQLException {
0622: checkOpen();
0623: nextToken();
0624:
0625: while (!endOfResponse && !currentToken.isUpdateCount()
0626: && !currentToken.isResultSet()) {
0627: nextToken();
0628: }
0629:
0630: //
0631: // Cursor opens are followed by TDS_TAB_INFO and TDS_COL_INFO
0632: // Process these now so that the column descriptors are updated.
0633: // Sybase wide result set headers are followed by a TDS_CONTROL_TOKEN
0634: // skip that as well.
0635: //
0636: if (currentToken.isResultSet()) {
0637: byte saveToken = currentToken.token;
0638: try {
0639: byte x = (byte) in.peek();
0640:
0641: while (x == TDS_TABNAME_TOKEN || x == TDS_COLINFO_TOKEN
0642: || x == TDS_CONTROL_TOKEN) {
0643: nextToken();
0644: x = (byte) in.peek();
0645: }
0646: } catch (IOException e) {
0647: connection.setClosed();
0648:
0649: throw Support.linkException(new SQLException(Messages
0650: .get("error.generic.ioerror", e.getMessage()),
0651: "08S01"), e);
0652: }
0653: currentToken.token = saveToken;
0654: }
0655:
0656: return currentToken.isResultSet();
0657: }
0658:
0659: /**
0660: * Retrieve the status of the next result item.
0661: *
0662: * @return <code>boolean</code> true if the next item is a result set.
0663: */
0664: boolean isResultSet() {
0665: return currentToken.isResultSet();
0666: }
0667:
0668: /**
0669: * Retrieve the status of the next result item.
0670: *
0671: * @return <code>boolean</code> true if the next item is row data.
0672: */
0673: boolean isRowData() {
0674: return currentToken.isRowData();
0675: }
0676:
0677: /**
0678: * Retrieve the status of the next result item.
0679: *
0680: * @return <code>boolean</code> true if the next item is an update count.
0681: */
0682: boolean isUpdateCount() {
0683: return currentToken.isUpdateCount();
0684: }
0685:
0686: /**
0687: * Retrieve the update count from the current TDS token.
0688: *
0689: * @return The update count as an <code>int</code>.
0690: */
0691: int getUpdateCount() {
0692: if (currentToken.isEndToken()) {
0693: return currentToken.updateCount;
0694: }
0695:
0696: return -1;
0697: }
0698:
0699: /**
0700: * Retrieve the status of the response stream.
0701: *
0702: * @return <code>boolean</code> true if the response has been entirely consumed
0703: */
0704: boolean isEndOfResponse() {
0705: return endOfResponse;
0706: }
0707:
0708: /**
0709: * Empty the server response queue.
0710: *
0711: * @throws SQLException if an error occurs
0712: */
0713: void clearResponseQueue() throws SQLException {
0714: checkOpen();
0715: while (!endOfResponse) {
0716: nextToken();
0717: }
0718: }
0719:
0720: /**
0721: * Consume packets from the server response queue up to (and including) the
0722: * first response terminator.
0723: *
0724: * @throws SQLException if an I/O or protocol error occurs; server errors
0725: * are queued up and not thrown
0726: */
0727: void consumeOneResponse() throws SQLException {
0728: checkOpen();
0729: while (!endOfResponse) {
0730: nextToken();
0731: // If it's a response terminator, return
0732: if (currentToken.isEndToken()
0733: && (currentToken.status & DONE_END_OF_RESPONSE) != 0) {
0734: return;
0735: }
0736: }
0737: }
0738:
0739: /**
0740: * Retrieve the next data row from the result set.
0741: *
0742: * @return <code>false</code> if at the end of results, <code>true</code>
0743: * otherwise
0744: * @throws SQLException if an I/O or protocol error occurs; server errors
0745: * are queued up and not thrown
0746: */
0747: boolean getNextRow() throws SQLException {
0748: if (endOfResponse || endOfResults) {
0749: return false;
0750: }
0751: checkOpen();
0752: nextToken();
0753:
0754: // Will either be first or next data row or end.
0755: while (!currentToken.isRowData() && !currentToken.isEndToken()) {
0756: nextToken(); // Could be messages
0757: }
0758:
0759: return currentToken.isRowData();
0760: }
0761:
0762: /**
0763: * Retrieve the status of result set.
0764: * <p>
0765: * This does a quick read ahead and is needed to support the isLast()
0766: * method in the ResultSet.
0767: *
0768: * @return <code>boolean</code> - <code>true</code> if there is more data
0769: * in the result set.
0770: */
0771: boolean isDataInResultSet() throws SQLException {
0772: byte x;
0773:
0774: checkOpen();
0775:
0776: try {
0777: x = (endOfResponse) ? TDS_DONE_TOKEN : (byte) in.peek();
0778:
0779: while (x != TDS_ROW_TOKEN && x != TDS_DONE_TOKEN
0780: && x != TDS_DONEINPROC_TOKEN
0781: && x != TDS_DONEPROC_TOKEN) {
0782: nextToken();
0783: x = (byte) in.peek();
0784: }
0785:
0786: messages.checkErrors();
0787: } catch (IOException e) {
0788: connection.setClosed();
0789: throw Support.linkException(new SQLException(Messages.get(
0790: "error.generic.ioerror", e.getMessage()), "08S01"),
0791: e);
0792: }
0793:
0794: return x == TDS_ROW_TOKEN;
0795: }
0796:
0797: /**
0798: * Retrieve the return status for the current stored procedure.
0799: *
0800: * @return The return status as an <code>Integer</code>.
0801: */
0802: Integer getReturnStatus() {
0803: return this .returnStatus;
0804: }
0805:
0806: /**
0807: * Inform the server that this connection is closing.
0808: * <p>
0809: * Used by Sybase a no-op for Microsoft.
0810: */
0811: synchronized void closeConnection() {
0812: try {
0813: if (tdsVersion == Driver.TDS50) {
0814: socket.setTimeout(1000);
0815: out.setPacketType(SYBQUERY_PKT);
0816: out.write((byte) TDS_CLOSE_TOKEN);
0817: out.write((byte) 0);
0818: out.flush();
0819: endOfResponse = false;
0820: clearResponseQueue();
0821: }
0822: } catch (Exception e) {
0823: // Ignore any exceptions as this connection
0824: // is closing anyway.
0825: }
0826: }
0827:
0828: /**
0829: * Close the <code>TdsCore</code> connection object and associated streams.
0830: */
0831: void close() throws SQLException {
0832: if (!isClosed) {
0833: try {
0834: clearResponseQueue();
0835: out.close();
0836: in.close();
0837: } finally {
0838: isClosed = true;
0839: }
0840: }
0841: }
0842:
0843: /**
0844: * Send (only) one cancel packet to the server.
0845: *
0846: * @param timeout true if this is a query timeout cancel
0847: */
0848: void cancel(boolean timeout) {
0849: Semaphore mutex = null;
0850: try {
0851: mutex = connection.getMutex();
0852: synchronized (cancelMonitor) {
0853: if (!cancelPending && !endOfResponse) {
0854: cancelPending = socket.cancel(out.getStreamId());
0855: }
0856: // If a cancel request was sent, reset the end of response flag
0857: if (cancelPending) {
0858: cancelMonitor[0] = timeout ? TIMEOUT_CANCEL
0859: : ASYNC_CANCEL;
0860: endOfResponse = false;
0861: }
0862: }
0863: } finally {
0864: if (mutex != null) {
0865: mutex.release();
0866: }
0867: }
0868: }
0869:
0870: /**
0871: * Submit a simple SQL statement to the server and process all output.
0872: *
0873: * @param sql the statement to execute
0874: * @throws SQLException if an error is returned by the server
0875: */
0876: void submitSQL(String sql) throws SQLException {
0877: checkOpen();
0878: messages.clearWarnings();
0879:
0880: if (sql.length() == 0) {
0881: throw new IllegalArgumentException(
0882: "submitSQL() called with empty SQL String");
0883: }
0884:
0885: executeSQL(sql, null, null, false, 0, -1, -1, true);
0886: clearResponseQueue();
0887: messages.checkErrors();
0888: }
0889:
0890: /**
0891: * Notifies the <code>TdsCore</code> that a batch is starting. This is so
0892: * that it knows to use <code>sp_executesql</code> for parameterized
0893: * queries (because there's no way to prepare a statement in the middle of
0894: * a batch).
0895: * <p>
0896: * Sets the {@link #inBatch} flag.
0897: */
0898: void startBatch() {
0899: inBatch = true;
0900: }
0901:
0902: /**
0903: * Send an SQL statement with optional parameters to the server.
0904: *
0905: * @param sql SQL statement to execute
0906: * @param procName stored procedure to execute or <code>null</code>
0907: * @param parameters parameters for call or null
0908: * @param noMetaData suppress meta data for cursor calls
0909: * @param timeOut optional query timeout or 0
0910: * @param maxRows the maximum number of data rows to return (-1 to
0911: * leave unaltered)
0912: * @param maxFieldSize the maximum number of bytes in a column to return
0913: * (-1 to leave unaltered)
0914: * @param sendNow whether to send the request now or not
0915: * @throws SQLException if an error occurs
0916: */
0917: synchronized void executeSQL(String sql, String procName,
0918: ParamInfo[] parameters, boolean noMetaData, int timeOut,
0919: int maxRows, int maxFieldSize, boolean sendNow)
0920: throws SQLException {
0921: boolean sendFailed = true; // Used to ensure mutex is released.
0922:
0923: try {
0924: //
0925: // Obtain a lock on the connection giving exclusive access
0926: // to the network connection for this thread
0927: //
0928: if (connectionLock == null) {
0929: connectionLock = connection.getMutex();
0930: }
0931: // Also checks if connection is open
0932: clearResponseQueue();
0933: messages.exceptions = null;
0934:
0935: //
0936: // Set the connection row count and text size if required.
0937: // Once set these will not be changed within a
0938: // batch so execution of the set rows query will
0939: // only occur once a the start of a batch.
0940: // No other thread can send until this one has finished.
0941: //
0942: setRowCountAndTextSize(maxRows, maxFieldSize);
0943:
0944: messages.clearWarnings();
0945: this .returnStatus = null;
0946: //
0947: // Normalize the parameters argument to simplify later checks
0948: //
0949: if (parameters != null && parameters.length == 0) {
0950: parameters = null;
0951: }
0952: this .parameters = parameters;
0953: //
0954: // Normalise the procName argument as well
0955: //
0956: if (procName != null && procName.length() == 0) {
0957: procName = null;
0958: }
0959:
0960: if (parameters != null && parameters[0].isRetVal) {
0961: returnParam = parameters[0];
0962: nextParam = 0;
0963: } else {
0964: returnParam = null;
0965: nextParam = -1;
0966: }
0967:
0968: if (parameters != null) {
0969: if (procName == null && sql.startsWith("EXECUTE ")) {
0970: //
0971: // If this is a callable statement that could not be fully parsed
0972: // into an RPC call convert to straight SQL now.
0973: // An example of non RPC capable SQL is {?=call sp_example('literal', ?)}
0974: //
0975: for (int i = 0; i < parameters.length; i++) {
0976: // Output parameters not allowed.
0977: if (!parameters[i].isRetVal
0978: && parameters[i].isOutput) {
0979: throw new SQLException(Messages.get(
0980: "error.prepare.nooutparam", Integer
0981: .toString(i + 1)), "07000");
0982: }
0983: }
0984: sql = Support.substituteParameters(sql, parameters,
0985: connection);
0986: parameters = null;
0987: } else {
0988: //
0989: // Check all parameters are either output or have values set
0990: //
0991: for (int i = 0; i < parameters.length; i++) {
0992: if (!parameters[i].isSet
0993: && !parameters[i].isOutput) {
0994: throw new SQLException(Messages.get(
0995: "error.prepare.paramnotset",
0996: Integer.toString(i + 1)), "07000");
0997: }
0998: parameters[i].clearOutValue();
0999: // FIXME Should only set TDS type if not already set
1000: // but we might need to take a lot of care not to
1001: // exceed size limitations (e.g. write 11 chars in a
1002: // VARCHAR(10) )
1003: TdsData
1004: .getNativeType(connection,
1005: parameters[i]);
1006: }
1007: }
1008: }
1009:
1010: try {
1011: switch (tdsVersion) {
1012: case Driver.TDS42:
1013: executeSQL42(sql, procName, parameters, noMetaData,
1014: sendNow);
1015: break;
1016: case Driver.TDS50:
1017: executeSQL50(sql, procName, parameters);
1018: break;
1019: case Driver.TDS70:
1020: case Driver.TDS80:
1021: case Driver.TDS81:
1022: executeSQL70(sql, procName, parameters, noMetaData,
1023: sendNow);
1024: break;
1025: default:
1026: throw new IllegalStateException(
1027: "Unknown TDS version " + tdsVersion);
1028: }
1029:
1030: if (sendNow) {
1031: out.flush();
1032: connectionLock.release();
1033: connectionLock = null;
1034: sendFailed = false;
1035: endOfResponse = false;
1036: endOfResults = true;
1037: wait(timeOut);
1038: } else {
1039: sendFailed = false;
1040: }
1041: } catch (IOException ioe) {
1042: connection.setClosed();
1043:
1044: throw Support.linkException(new SQLException(
1045: Messages.get("error.generic.ioerror", ioe
1046: .getMessage()), "08S01"), ioe);
1047: }
1048: } finally {
1049: if ((sendNow || sendFailed) && connectionLock != null) {
1050: connectionLock.release();
1051: connectionLock = null;
1052: }
1053: // Clear the in batch flag
1054: if (sendNow) {
1055: inBatch = false;
1056: }
1057: }
1058: }
1059:
1060: /**
1061: * Prepares the SQL for use with Microsoft server.
1062: *
1063: * @param sql the SQL statement to prepare.
1064: * @param params the actual parameter list
1065: * @param needCursor true if a cursorprepare is required
1066: * @param resultSetType value of the resultSetType parameter when
1067: * the Statement was created
1068: * @param resultSetConcurrency value of the resultSetConcurrency parameter
1069: * whenthe Statement was created
1070: * @return name of the procedure or prepared statement handle.
1071: * @exception SQLException
1072: */
1073: String microsoftPrepare(String sql, ParamInfo[] params,
1074: boolean needCursor, int resultSetType,
1075: int resultSetConcurrency) throws SQLException {
1076: //
1077: checkOpen();
1078: messages.clearWarnings();
1079:
1080: int prepareSql = connection.getPrepareSql();
1081:
1082: if (prepareSql == TEMPORARY_STORED_PROCEDURES) {
1083: StringBuffer spSql = new StringBuffer(sql.length() + 32
1084: + params.length * 15);
1085: String procName = connection.getProcName();
1086:
1087: spSql.append("create proc ");
1088: spSql.append(procName);
1089: spSql.append(' ');
1090:
1091: for (int i = 0; i < params.length; i++) {
1092: spSql.append("@P");
1093: spSql.append(i);
1094: spSql.append(' ');
1095: spSql.append(params[i].sqlType);
1096:
1097: if (i + 1 < params.length) {
1098: spSql.append(',');
1099: }
1100: }
1101:
1102: // continue building proc
1103: spSql.append(" as ");
1104: spSql.append(Support.substituteParamMarkers(sql, params));
1105:
1106: try {
1107: submitSQL(spSql.toString());
1108: return procName;
1109: } catch (SQLException e) {
1110: if ("08S01".equals(e.getSQLState())) {
1111: // Serious (I/O) error, rethrow
1112: throw e;
1113: }
1114:
1115: // This exception probably caused by failure to prepare
1116: // Add a warning
1117: messages.addWarning(Support.linkException(
1118: new SQLWarning(Messages.get(
1119: "error.prepare.prepfailed", e
1120: .getMessage()),
1121: e.getSQLState(), e.getErrorCode()), e));
1122: }
1123:
1124: } else if (prepareSql == PREPARE) {
1125: int scrollOpt, ccOpt;
1126:
1127: ParamInfo prepParam[] = new ParamInfo[needCursor ? 6 : 4];
1128:
1129: // Setup prepare handle param
1130: prepParam[0] = new ParamInfo(Types.INTEGER, null,
1131: ParamInfo.OUTPUT);
1132:
1133: // Setup parameter descriptor param
1134: prepParam[1] = new ParamInfo(Types.LONGVARCHAR, Support
1135: .getParameterDefinitions(params), ParamInfo.UNICODE);
1136:
1137: // Setup sql statemement param
1138: prepParam[2] = new ParamInfo(Types.LONGVARCHAR, Support
1139: .substituteParamMarkers(sql, params),
1140: ParamInfo.UNICODE);
1141:
1142: // Setup options param
1143: prepParam[3] = new ParamInfo(Types.INTEGER, new Integer(1),
1144: ParamInfo.INPUT);
1145:
1146: if (needCursor) {
1147: // Select the correct type of Server side cursor to
1148: // match the scroll and concurrency options.
1149: scrollOpt = MSCursorResultSet.getCursorScrollOpt(
1150: resultSetType, resultSetConcurrency, true);
1151: ccOpt = MSCursorResultSet
1152: .getCursorConcurrencyOpt(resultSetConcurrency);
1153:
1154: // Setup scroll options parameter
1155: prepParam[4] = new ParamInfo(Types.INTEGER,
1156: new Integer(scrollOpt), ParamInfo.OUTPUT);
1157:
1158: // Setup concurrency options parameter
1159: prepParam[5] = new ParamInfo(Types.INTEGER,
1160: new Integer(ccOpt), ParamInfo.OUTPUT);
1161: }
1162:
1163: columns = null; // Will be populated if preparing a select
1164: try {
1165: executeSQL(null, needCursor ? "sp_cursorprepare"
1166: : "sp_prepare", prepParam, false, 0, -1, -1,
1167: true);
1168:
1169: int resultCount = 0;
1170: while (!endOfResponse) {
1171: nextToken();
1172: if (isResultSet()) {
1173: resultCount++;
1174: }
1175: }
1176: // columns will now hold meta data for any select statements
1177: if (resultCount != 1) {
1178: // More than one result set was returned or none
1179: // therefore metadata not available or unsafe.
1180: columns = null;
1181: }
1182: Integer prepareHandle = (Integer) prepParam[0]
1183: .getOutValue();
1184: if (prepareHandle != null) {
1185: return prepareHandle.toString();
1186: }
1187: // Probably an exception occured, check for it
1188: messages.checkErrors();
1189: } catch (SQLException e) {
1190: if ("08S01".equals(e.getSQLState())) {
1191: // Serious (I/O) error, rethrow
1192: throw e;
1193: }
1194: // This exception probably caused by failure to prepare
1195: // Add a warning
1196: messages.addWarning(Support.linkException(
1197: new SQLWarning(Messages.get(
1198: "error.prepare.prepfailed", e
1199: .getMessage()),
1200: e.getSQLState(), e.getErrorCode()), e));
1201: }
1202: }
1203:
1204: return null;
1205: }
1206:
1207: /**
1208: * Creates a light weight stored procedure on a Sybase server.
1209: *
1210: * @param sql SQL statement to prepare
1211: * @param params the actual parameter list
1212: * @return name of the procedure
1213: * @throws SQLException if an error occurs
1214: */
1215: synchronized String sybasePrepare(String sql, ParamInfo[] params)
1216: throws SQLException {
1217: checkOpen();
1218: messages.clearWarnings();
1219: if (sql == null || sql.length() == 0) {
1220: throw new IllegalArgumentException(
1221: "sql parameter must be at least 1 character long.");
1222: }
1223:
1224: String procName = connection.getProcName();
1225:
1226: if (procName == null || procName.length() != 11) {
1227: throw new IllegalArgumentException(
1228: "procName parameter must be 11 characters long.");
1229: }
1230:
1231: // TODO Check if output parameters are handled ok
1232: // Check no text/image parameters
1233: for (int i = 0; i < params.length; i++) {
1234: if ("text".equals(params[i].sqlType)
1235: || "unitext".equals(params[i].sqlType)
1236: || "image".equals(params[i].sqlType)) {
1237: return null; // Sadly no way
1238: }
1239: }
1240:
1241: Semaphore mutex = null;
1242:
1243: try {
1244: mutex = connection.getMutex();
1245:
1246: out.setPacketType(SYBQUERY_PKT);
1247: out.write((byte) TDS5_DYNAMIC_TOKEN);
1248:
1249: byte buf[] = Support.encodeString(connection.getCharset(),
1250: sql);
1251:
1252: out.write((short) (buf.length + 41));
1253: out.write((byte) 1);
1254: out.write((byte) 0);
1255: out.write((byte) 10);
1256: out.writeAscii(procName.substring(1));
1257: out.write((short) (buf.length + 26));
1258: out.writeAscii("create proc ");
1259: out.writeAscii(procName.substring(1));
1260: out.writeAscii(" as ");
1261: out.write(buf);
1262: out.flush();
1263: endOfResponse = false;
1264: clearResponseQueue();
1265: messages.checkErrors();
1266: return procName;
1267: } catch (IOException ioe) {
1268: connection.setClosed();
1269: throw Support.linkException(
1270: new SQLException(Messages.get(
1271: "error.generic.ioerror", ioe.getMessage()),
1272: "08S01"), ioe);
1273: } catch (SQLException e) {
1274: if ("08S01".equals(e.getSQLState())) {
1275: // Serious error rethrow
1276: throw e;
1277: }
1278:
1279: // This exception probably caused by failure to prepare
1280: // Return null;
1281: return null;
1282: } finally {
1283: if (mutex != null) {
1284: mutex.release();
1285: }
1286: }
1287: }
1288:
1289: /**
1290: * Drops a Sybase temporary stored procedure.
1291: *
1292: * @param procName the temporary procedure name
1293: * @throws SQLException if an error occurs
1294: */
1295: synchronized void sybaseUnPrepare(String procName)
1296: throws SQLException {
1297: checkOpen();
1298: messages.clearWarnings();
1299:
1300: if (procName == null || procName.length() != 11) {
1301: throw new IllegalArgumentException(
1302: "procName parameter must be 11 characters long.");
1303: }
1304:
1305: Semaphore mutex = null;
1306: try {
1307: mutex = connection.getMutex();
1308:
1309: out.setPacketType(SYBQUERY_PKT);
1310: out.write((byte) TDS5_DYNAMIC_TOKEN);
1311: out.write((short) (15));
1312: out.write((byte) 4);
1313: out.write((byte) 0);
1314: out.write((byte) 10);
1315: out.writeAscii(procName.substring(1));
1316: out.write((short) 0);
1317: out.flush();
1318: endOfResponse = false;
1319: clearResponseQueue();
1320: messages.checkErrors();
1321: } catch (IOException ioe) {
1322: connection.setClosed();
1323: throw Support.linkException(
1324: new SQLException(Messages.get(
1325: "error.generic.ioerror", ioe.getMessage()),
1326: "08S01"), ioe);
1327: } catch (SQLException e) {
1328: if ("08S01".equals(e.getSQLState())) {
1329: // Serious error rethrow
1330: throw e;
1331: }
1332: // This exception probably caused by failure to unprepare
1333: } finally {
1334: if (mutex != null) {
1335: mutex.release();
1336: }
1337: }
1338: }
1339:
1340: /**
1341: * Enlist the current connection in a distributed transaction or request the location of the
1342: * MSDTC instance controlling the server we are connected to.
1343: *
1344: * @param type set to 0 to request TM address or 1 to enlist connection
1345: * @param oleTranID the 40 OLE transaction ID
1346: * @return a <code>byte[]</code> array containing the TM address data
1347: * @throws SQLException
1348: */
1349: synchronized byte[] enlistConnection(int type, byte[] oleTranID)
1350: throws SQLException {
1351: Semaphore mutex = null;
1352: try {
1353: mutex = connection.getMutex();
1354:
1355: out.setPacketType(MSDTC_PKT);
1356: out.write((short) type);
1357: switch (type) {
1358: case 0: // Get result set with location of MSTDC
1359: out.write((short) 0);
1360: break;
1361: case 1: // Set OLE transaction ID
1362: if (oleTranID != null) {
1363: out.write((short) oleTranID.length);
1364: out.write(oleTranID);
1365: } else {
1366: // Delist the connection from all transactions.
1367: out.write((short) 0);
1368: }
1369: break;
1370: }
1371: out.flush();
1372: endOfResponse = false;
1373: endOfResults = true;
1374: } catch (IOException ioe) {
1375: connection.setClosed();
1376: throw Support.linkException(
1377: new SQLException(Messages.get(
1378: "error.generic.ioerror", ioe.getMessage()),
1379: "08S01"), ioe);
1380: } finally {
1381: if (mutex != null) {
1382: mutex.release();
1383: }
1384: }
1385:
1386: byte[] tmAddress = null;
1387: if (getMoreResults() && getNextRow()) {
1388: if (rowData.length == 1) {
1389: Object x = rowData[0];
1390: if (x instanceof byte[]) {
1391: tmAddress = (byte[]) x;
1392: }
1393: }
1394: }
1395:
1396: clearResponseQueue();
1397: messages.checkErrors();
1398: return tmAddress;
1399: }
1400:
1401: /**
1402: * Obtain the counts from a batch of SQL updates.
1403: * <p/>
1404: * If an error occurs Sybase will continue processing a batch consisting of
1405: * TDS_LANGUAGE records whilst SQL Server will usually stop after the first
1406: * error except when the error is caused by a duplicate key.
1407: * Sybase will also stop after the first error when executing RPC calls.
1408: * Care is taken to ensure that <code>SQLException</code>s are chained
1409: * because there could be several errors reported in a batch.
1410: *
1411: * @param counts the <code>ArrayList</code> containing the update counts
1412: * @param sqlEx any previous <code>SQLException</code>(s) encountered
1413: * @return updated <code>SQLException</code> or <code>null</code> if no
1414: * error has yet occured
1415: */
1416: SQLException getBatchCounts(ArrayList counts, SQLException sqlEx) {
1417: Integer lastCount = JtdsStatement.SUCCESS_NO_INFO;
1418:
1419: try {
1420: checkOpen();
1421: while (!endOfResponse) {
1422: nextToken();
1423: if (currentToken.isResultSet()) {
1424: // Serious error, statement must not return a result set
1425: throw new SQLException(Messages
1426: .get("error.statement.batchnocount"),
1427: "07000");
1428: }
1429: //
1430: // Analyse type of end token and try to extract correct
1431: // update count when calling stored procs.
1432: //
1433: switch (currentToken.token) {
1434: case TDS_DONE_TOKEN:
1435: if ((currentToken.status & DONE_ERROR) != 0
1436: || lastCount == JtdsStatement.EXECUTE_FAILED) {
1437: counts.add(JtdsStatement.EXECUTE_FAILED);
1438: } else {
1439: if (currentToken.isUpdateCount()) {
1440: counts.add(new Integer(
1441: currentToken.updateCount));
1442: } else {
1443: counts.add(lastCount);
1444: }
1445: }
1446: lastCount = JtdsStatement.SUCCESS_NO_INFO;
1447: break;
1448: case TDS_DONEINPROC_TOKEN:
1449: if ((currentToken.status & DONE_ERROR) != 0) {
1450: lastCount = JtdsStatement.EXECUTE_FAILED;
1451: } else if (currentToken.isUpdateCount()) {
1452: lastCount = new Integer(
1453: currentToken.updateCount);
1454: }
1455: break;
1456: case TDS_DONEPROC_TOKEN:
1457: if ((currentToken.status & DONE_ERROR) != 0
1458: || lastCount == JtdsStatement.EXECUTE_FAILED) {
1459: counts.add(JtdsStatement.EXECUTE_FAILED);
1460: } else {
1461: counts.add(lastCount);
1462: }
1463: lastCount = JtdsStatement.SUCCESS_NO_INFO;
1464: break;
1465: }
1466: }
1467: //
1468: // Check for any exceptions
1469: //
1470: messages.checkErrors();
1471:
1472: } catch (SQLException e) {
1473: //
1474: // Chain all exceptions
1475: //
1476: if (sqlEx != null) {
1477: sqlEx.setNextException(e);
1478: } else {
1479: sqlEx = e;
1480: }
1481: } finally {
1482: while (!endOfResponse) {
1483: // Flush rest of response
1484: try {
1485: nextToken();
1486: } catch (SQLException ex) {
1487: // Chain any exceptions to the BatchUpdateException
1488: if (sqlEx != null) {
1489: sqlEx.setNextException(ex);
1490: } else {
1491: sqlEx = ex;
1492: }
1493: }
1494: }
1495: }
1496:
1497: return sqlEx;
1498: }
1499:
1500: // ---------------------- Private Methods from here ---------------------
1501:
1502: /**
1503: * Write a TDS login packet string. Text followed by padding followed
1504: * by a byte sized length.
1505: */
1506: private void putLoginString(String txt, int len) throws IOException {
1507: byte[] tmp = Support.encodeString(connection.getCharset(), txt);
1508: out.write(tmp, 0, len);
1509: out.write((byte) (tmp.length < len ? tmp.length : len));
1510: }
1511:
1512: /**
1513: * Send the SQL Server 2000 pre login packet.
1514: * <p>Packet contains; netlib version, ssl mode, instance
1515: * and process ID.
1516: * @param instance
1517: * @param forceEncryption
1518: * @throws IOException
1519: */
1520: private void sendPreLoginPacket(String instance,
1521: boolean forceEncryption) throws IOException {
1522: out.setPacketType(PRELOGIN_PKT);
1523: // Write Netlib pointer
1524: out.write((short) 0);
1525: out.write((short) 21);
1526: out.write((byte) 6);
1527: // Write Encrypt flag pointer
1528: out.write((short) 1);
1529: out.write((short) 27);
1530: out.write((byte) 1);
1531: // Write Instance name pointer
1532: out.write((short) 2);
1533: out.write((short) 28);
1534: out.write((byte) (instance.length() + 1));
1535: // Write process ID pointer
1536: out.write((short) 3);
1537: out.write((short) (28 + instance.length() + 1));
1538: out.write((byte) 4);
1539: // Write terminator
1540: out.write((byte) 0xFF);
1541: // Write fake net lib ID 8.341.0
1542: out.write(new byte[] { 0x08, 0x00, 0x01, 0x55, 0x00, 0x00 });
1543: // Write force encryption flag
1544: out.write((byte) (forceEncryption ? 1 : 0));
1545: // Write instance name
1546: out.writeAscii(instance);
1547: out.write((byte) 0);
1548: // Write dummy process ID
1549: out.write(new byte[] { 0x01, 0x02, 0x00, 0x00 });
1550: //
1551: out.flush();
1552: }
1553:
1554: /**
1555: * Process the pre login acknowledgement from the server.
1556: * <p>Packet contains; server version no, SSL mode, instance name
1557: * and process id.
1558: * <p>Server returns the following values for SSL mode:
1559: * <ol>
1560: * <ll>0 = Certificate installed encrypt login packet only.
1561: * <li>1 = Certificate installed client requests force encryption.
1562: * <li>2 = No certificate no encryption possible.
1563: * <li>3 = Server requests force encryption.
1564: * </ol>
1565: * @return The server side SSL mode.
1566: * @throws IOException
1567: */
1568: private int readPreLoginPacket() throws IOException {
1569: byte list[][] = new byte[8][];
1570: byte data[][] = new byte[8][];
1571: int recordCount = 0;
1572:
1573: byte record[] = new byte[5];
1574: // Read entry pointers
1575: record[0] = (byte) in.read();
1576: while ((record[0] & 0xFF) != 0xFF) {
1577: if (recordCount == list.length) {
1578: throw new IOException(
1579: "Pre Login packet has more than 8 entries");
1580: }
1581: // Read record
1582: in.read(record, 1, 4);
1583: list[recordCount++] = record;
1584: record = new byte[5];
1585: record[0] = (byte) in.read();
1586: }
1587: // Read entry data
1588: for (int i = 0; i < recordCount; i++) {
1589: byte value[] = new byte[(byte) list[i][4]];
1590: in.read(value);
1591: data[i] = value;
1592: }
1593: if (Logger.isActive()) {
1594: // Diagnostic dump
1595: Logger.println("PreLogin server response");
1596: for (int i = 0; i < recordCount; i++) {
1597: Logger.println("Record " + i + " = "
1598: + Support.toHex(data[i]));
1599: }
1600: }
1601: if (recordCount > 1) {
1602: return data[1][0]; // This is the server side SSL mode
1603: } else {
1604: // Response too short to include SSL mode!
1605: return SSL_NO_ENCRYPT;
1606: }
1607: }
1608:
1609: /**
1610: * TDS 4.2 Login Packet.
1611: *
1612: * @param serverName server host name
1613: * @param user user name
1614: * @param password user password
1615: * @param charset required server character set
1616: * @param appName application name
1617: * @param progName program name
1618: * @param wsid workstation ID
1619: * @param language server language for messages
1620: * @param packetSize required network packet size
1621: * @throws IOException if an I/O error occurs
1622: */
1623: private void send42LoginPkt(final String serverName,
1624: final String user, final String password,
1625: final String charset, final String appName,
1626: final String progName, final String wsid,
1627: final String language, final int packetSize)
1628: throws IOException {
1629: final byte[] empty = new byte[0];
1630:
1631: out.setPacketType(LOGIN_PKT);
1632: putLoginString(wsid, 30); // Host name
1633: putLoginString(user, 30); // user name
1634: putLoginString(password, 30); // password
1635: putLoginString("00000123", 30); // hostproc (offset 93 0x5d)
1636:
1637: out.write((byte) 3); // type of int2
1638: out.write((byte) 1); // type of int4
1639: out.write((byte) 6); // type of char
1640: out.write((byte) 10);// type of flt
1641: out.write((byte) 9); // type of date
1642: out.write((byte) 1); // notify of use db
1643: out.write((byte) 1); // disallow dump/load and bulk insert
1644: out.write((byte) 0); // sql interface type
1645: out.write((byte) 0); // type of network connection
1646:
1647: out.write(empty, 0, 7);
1648:
1649: putLoginString(appName, 30); // appname
1650: putLoginString(serverName, 30); // server name
1651:
1652: out.write((byte) 0); // remote passwords
1653: out.write((byte) password.length());
1654: byte[] tmp = Support.encodeString(connection.getCharset(),
1655: password);
1656: out.write(tmp, 0, 253);
1657: out.write((byte) (tmp.length + 2));
1658:
1659: out.write((byte) 4); // tds version
1660: out.write((byte) 2);
1661:
1662: out.write((byte) 0);
1663: out.write((byte) 0);
1664: putLoginString(progName, 10); // prog name
1665:
1666: out.write((byte) 6); // prog version
1667: out.write((byte) 0);
1668: out.write((byte) 0);
1669: out.write((byte) 0);
1670:
1671: out.write((byte) 0); // auto convert short
1672: out.write((byte) 0x0D); // type of flt4
1673: out.write((byte) 0x11); // type of date4
1674:
1675: putLoginString(language, 30); // language
1676:
1677: out.write((byte) 1); // notify on lang change
1678: out.write((short) 0); // security label hierachy
1679: out.write((byte) 0); // security encrypted
1680: out.write(empty, 0, 8); // security components
1681: out.write((short) 0); // security spare
1682:
1683: putLoginString(charset, 30); // Character set
1684:
1685: out.write((byte) 1); // notify on charset change
1686: putLoginString(String.valueOf(packetSize), 6); // length of tds packets
1687:
1688: out.write(empty, 0, 8); // pad out to a longword
1689:
1690: out.flush(); // Send the packet
1691: endOfResponse = false;
1692: }
1693:
1694: /**
1695: * TDS 5.0 Login Packet.
1696: * <P>
1697: * @param serverName server host name
1698: * @param user user name
1699: * @param password user password
1700: * @param charset required server character set
1701: * @param appName application name
1702: * @param progName library name
1703: * @param wsid workstation ID
1704: * @param language server language for messages
1705: * @param packetSize required network packet size
1706: * @throws IOException if an I/O error occurs
1707: */
1708: private void send50LoginPkt(final String serverName,
1709: final String user, final String password,
1710: final String charset, final String appName,
1711: final String progName, final String wsid,
1712: final String language, final int packetSize)
1713: throws IOException {
1714: final byte[] empty = new byte[0];
1715:
1716: out.setPacketType(LOGIN_PKT);
1717: putLoginString(wsid, 30); // Host name
1718: putLoginString(user, 30); // user name
1719: putLoginString(password, 30); // password
1720: putLoginString("00000123", 30); // hostproc (offset 93 0x5d)
1721:
1722: out.write((byte) 3); // type of int2
1723: out.write((byte) 1); // type of int4
1724: out.write((byte) 6); // type of char
1725: out.write((byte) 10);// type of flt
1726: out.write((byte) 9); // type of date
1727: out.write((byte) 1); // notify of use db
1728: out.write((byte) 1); // disallow dump/load and bulk insert
1729: out.write((byte) 0); // sql interface type
1730: out.write((byte) 0); // type of network connection
1731:
1732: out.write(empty, 0, 7);
1733:
1734: putLoginString(appName, 30); // appname
1735: putLoginString(serverName, 30); // server name
1736: out.write((byte) 0); // remote passwords
1737: out.write((byte) password.length());
1738: byte[] tmp = Support.encodeString(connection.getCharset(),
1739: password);
1740: out.write(tmp, 0, 253);
1741: out.write((byte) (tmp.length + 2));
1742:
1743: out.write((byte) 5); // tds version
1744: out.write((byte) 0);
1745:
1746: out.write((byte) 0);
1747: out.write((byte) 0);
1748: putLoginString(progName, 10); // prog name
1749:
1750: out.write((byte) 5); // prog version
1751: out.write((byte) 0);
1752: out.write((byte) 0);
1753: out.write((byte) 0);
1754:
1755: out.write((byte) 0); // auto convert short
1756: out.write((byte) 0x0D); // type of flt4
1757: out.write((byte) 0x11); // type of date4
1758:
1759: putLoginString(language, 30); // language
1760:
1761: out.write((byte) 1); // notify on lang change
1762: out.write((short) 0); // security label hierachy
1763: out.write((byte) 0); // security encrypted
1764: out.write(empty, 0, 8); // security components
1765: out.write((short) 0); // security spare
1766:
1767: putLoginString(charset, 30); // Character set
1768:
1769: out.write((byte) 1); // notify on charset change
1770: if (packetSize > 0) {
1771: putLoginString(String.valueOf(packetSize), 6); // specified length of tds packets
1772: } else {
1773: putLoginString(String.valueOf(MIN_PKT_SIZE), 6); // Default length of tds packets
1774: }
1775: out.write(empty, 0, 4);
1776: //
1777: // Request capabilities
1778: //
1779: // jTDS sends 01 0B 4F FF 85 EE EF 65 7F FF FF FF D6
1780: // Sybase 11.92 01 0A 00 00 00 23 61 41 CF FF FF C6
1781: // Sybase 12.52 01 0A 03 84 0A E3 61 41 FF FF FF C6
1782: // Sybase 15.00 01 0B 4F F7 85 EA EB 61 7F FF FF FF C6
1783: //
1784: // Response capabilities
1785: //
1786: // jTDS sends 02 0A 00 00 04 06 80 06 48 00 00 00
1787: // Sybase 11.92 02 0A 00 00 00 00 00 06 00 00 00 00
1788: // Sybase 12.52 02 0A 00 00 00 00 00 06 00 00 00 00
1789: // Sybase 15.00 02 0A 00 00 04 00 00 06 00 00 00 00
1790: //
1791: byte capString[] = {
1792: // Request capabilities
1793: (byte) 0x01, (byte) 0x0B, (byte) 0x4F, (byte) 0xFF,
1794: (byte) 0x85, (byte) 0xEE, (byte) 0xEF, (byte) 0x65,
1795: (byte) 0x7F, (byte) 0xFF, (byte) 0xFF,
1796: (byte) 0xFF,
1797: (byte) 0xD6,
1798: // Response capabilities
1799: (byte) 0x02, (byte) 0x0A, (byte) 0x00, (byte) 0x02,
1800: (byte) 0x04, (byte) 0x06, (byte) 0x80, (byte) 0x06,
1801: (byte) 0x48, (byte) 0x00, (byte) 0x00, (byte) 0x0C };
1802:
1803: if (packetSize == 0) {
1804: // Tell the server we will use its packet size
1805: capString[17] = 0;
1806: }
1807: out.write(TDS_CAP_TOKEN);
1808: out.write((short) capString.length);
1809: out.write(capString);
1810:
1811: out.flush(); // Send the packet
1812: endOfResponse = false;
1813: }
1814:
1815: /**
1816: * Send a TDS 7 login packet.
1817: * <p>
1818: * This method incorporates the Windows single sign on code contributed by
1819: * Magendran Sathaiah. To invoke single sign on just leave the user name
1820: * blank or null. NB. This can only work if the driver is being executed on
1821: * a Windows PC and <code>ntlmauth.dll</code> is on the path.
1822: *
1823: * @param serverName server host name
1824: * @param database required database
1825: * @param user user name
1826: * @param password user password
1827: * @param domain Windows NT domain (or <code>null</code>)
1828: * @param appName application name
1829: * @param progName program name
1830: * @param wsid workstation ID
1831: * @param language server language for messages
1832: * @param macAddress client network MAC address
1833: * @param netPacketSize TDS packet size to use
1834: * @throws IOException if an I/O error occurs
1835: */
1836: private void sendMSLoginPkt(final String serverName,
1837: final String database, final String user,
1838: final String password, final String domain,
1839: final String appName, final String progName,
1840: final String wsid, final String language,
1841: final String macAddress, final int netPacketSize)
1842: throws IOException, SQLException {
1843: final byte[] empty = new byte[0];
1844: boolean ntlmAuth = false;
1845: byte[] ntlmMessage = null;
1846:
1847: if (user == null || user.length() == 0) {
1848: // See if executing on a Windows platform and if so try and
1849: // use the single sign on native library.
1850: if (Support.isWindowsOS()) {
1851: ntlmAuthSSO = true;
1852: ntlmAuth = true;
1853: } else {
1854: throw new SQLException(Messages
1855: .get("error.connection.sso"), "08001");
1856: }
1857: } else if (domain != null && domain.length() > 0) {
1858: // Assume we want to use Windows authentication with
1859: // supplied user and password.
1860: ntlmAuth = true;
1861: }
1862:
1863: if (ntlmAuthSSO) {
1864: try {
1865: // Create the NTLM request block using the native library
1866: sspiJNIClient = SSPIJNIClient.getInstance();
1867: ntlmMessage = sspiJNIClient.invokePrepareSSORequest();
1868: } catch (Exception e) {
1869: throw new IOException("SSO Failed: " + e.getMessage());
1870: }
1871: }
1872:
1873: //mdb:begin-change
1874: short packSize = (short) (86 + 2 * (wsid.length()
1875: + appName.length() + serverName.length()
1876: + progName.length() + database.length() + language
1877: .length()));
1878: final short authLen;
1879: //NOTE(mdb): ntlm includes auth block; sql auth includes uname and pwd.
1880: if (ntlmAuth) {
1881: if (ntlmAuthSSO && ntlmMessage != null) {
1882: authLen = (short) ntlmMessage.length;
1883: } else {
1884: authLen = (short) (32 + domain.length());
1885: }
1886: packSize += authLen;
1887: } else {
1888: authLen = 0;
1889: packSize += (2 * (user.length() + password.length()));
1890: }
1891: //mdb:end-change
1892:
1893: out.setPacketType(MSLOGIN_PKT);
1894: out.write((int) packSize);
1895: // TDS version
1896: if (tdsVersion == Driver.TDS70) {
1897: out.write((int) 0x70000000);
1898: } else {
1899: out.write((int) 0x71000001);
1900: }
1901: // Network Packet size requested by client
1902: out.write((int) netPacketSize);
1903: // Program version?
1904: out.write((int) 7);
1905: // Process ID
1906: out.write((int) 123);
1907: // Connection ID
1908: out.write((int) 0);
1909: // 0x20: enable warning messages if USE <database> issued
1910: // 0x40: change to initial database must succeed
1911: // 0x80: enable warning messages if SET LANGUAGE issued
1912: byte flags = (byte) (0x20 | 0x40 | 0x80);
1913: out.write(flags);
1914:
1915: //mdb: this byte controls what kind of auth we do.
1916: flags = 0x03; // ODBC (JDBC) driver
1917: if (ntlmAuth)
1918: flags |= 0x80; // Use NT authentication
1919: out.write(flags);
1920:
1921: out.write((byte) 0); // SQL type flag
1922: out.write((byte) 0); // Reserved flag
1923: // TODO Set Timezone and collation?
1924: out.write(empty, 0, 4); // Time Zone
1925: out.write(empty, 0, 4); // Collation
1926:
1927: // Pack up value lengths, positions.
1928: short curPos = 86;
1929:
1930: // Hostname
1931: out.write((short) curPos);
1932: out.write((short) wsid.length());
1933: curPos += wsid.length() * 2;
1934:
1935: //mdb: NTLM doesn't send username and password...
1936: if (!ntlmAuth) {
1937: // Username
1938: out.write((short) curPos);
1939: out.write((short) user.length());
1940: curPos += user.length() * 2;
1941:
1942: // Password
1943: out.write((short) curPos);
1944: out.write((short) password.length());
1945: curPos += password.length() * 2;
1946: } else {
1947: out.write((short) curPos);
1948: out.write((short) 0);
1949:
1950: out.write((short) curPos);
1951: out.write((short) 0);
1952: }
1953:
1954: // App name
1955: out.write((short) curPos);
1956: out.write((short) appName.length());
1957: curPos += appName.length() * 2;
1958:
1959: // Server name
1960: out.write((short) curPos);
1961: out.write((short) serverName.length());
1962: curPos += serverName.length() * 2;
1963:
1964: // Unknown
1965: out.write((short) 0);
1966: out.write((short) 0);
1967:
1968: // Program name
1969: out.write((short) curPos);
1970: out.write((short) progName.length());
1971: curPos += progName.length() * 2;
1972:
1973: // Server language
1974: out.write((short) curPos);
1975: out.write((short) language.length());
1976: curPos += language.length() * 2;
1977:
1978: // Database
1979: out.write((short) curPos);
1980: out.write((short) database.length());
1981: curPos += database.length() * 2;
1982:
1983: // MAC address
1984: out.write(getMACAddress(macAddress));
1985:
1986: //mdb: location of ntlm auth block. note that for sql auth, authLen==0.
1987: out.write((short) curPos);
1988: out.write((short) authLen);
1989:
1990: //"next position" (same as total packet size)
1991: out.write((int) packSize);
1992:
1993: out.write(wsid);
1994:
1995: // Pack up the login values.
1996: //mdb: for ntlm auth, uname and pwd aren't sent up...
1997: if (!ntlmAuth) {
1998: final String scrambledPw = tds7CryptPass(password);
1999: out.write(user);
2000: out.write(scrambledPw);
2001: }
2002:
2003: out.write(appName);
2004: out.write(serverName);
2005: out.write(progName);
2006: out.write(language);
2007: out.write(database);
2008:
2009: //mdb: add the ntlm auth info...
2010: if (ntlmAuth) {
2011: if (ntlmAuthSSO) {
2012: // Use the NTLM message generated by the native library
2013: out.write(ntlmMessage);
2014: } else {
2015: // host and domain name are _narrow_ strings.
2016: final byte[] domainBytes = domain.getBytes("UTF8");
2017: //byte[] hostBytes = localhostname.getBytes("UTF8");
2018:
2019: final byte[] header = { 0x4e, 0x54, 0x4c, 0x4d, 0x53,
2020: 0x53, 0x50, 0x00 };
2021: out.write(header); //header is ascii "NTLMSSP\0"
2022: out.write((int) 1); //sequence number = 1
2023: if (connection.getUseNTLMv2())
2024: out.write((int) 0x8b205); //flags (same as below, only with Request Target and NTLM2 set)
2025: else
2026: out.write((int) 0xb201); //flags (see below)
2027:
2028: // NOTE: flag reference:
2029: // 0x80000 = negotiate NTLM2 key
2030: // 0x08000 = negotiate always sign
2031: // 0x02000 = client is sending workstation name
2032: // 0x01000 = client is sending domain name
2033: // 0x00200 = negotiate NTLM
2034: // 0x00004 - Request Target, which requests that server send target
2035: // 0x00001 = negotiate Unicode
2036:
2037: //domain info
2038: out.write((short) domainBytes.length);
2039: out.write((short) domainBytes.length);
2040: out.write((int) 32); //offset, relative to start of auth block.
2041:
2042: //host info
2043: //NOTE(mdb): not sending host info; hope this is ok!
2044: out.write((short) 0);
2045: out.write((short) 0);
2046: out.write((int) 32); //offset, relative to start of auth block.
2047:
2048: // add the variable length data at the end...
2049: out.write(domainBytes);
2050: }
2051: }
2052: out.flush(); // Send the packet
2053: endOfResponse = false;
2054: }
2055:
2056: /**
2057: * Send the response to the NTLM authentication challenge.
2058: * @param nonce The secret to hash with password.
2059: * @param user The user name.
2060: * @param password The user password.
2061: * @param domain The Windows NT Dommain.
2062: * @throws java.io.IOException
2063: */
2064: private void sendNtlmChallengeResponse(final byte[] nonce,
2065: String user, final String password, String domain)
2066: throws java.io.IOException {
2067: out.setPacketType(NTLMAUTH_PKT);
2068:
2069: // Prepare and Set NTLM Type 2 message appropriately
2070: // Author: mahi@aztec.soft.net
2071: if (ntlmAuthSSO) {
2072: byte[] ntlmMessage = currentToken.ntlmMessage;
2073: try {
2074: // Create the challenge response using the native library
2075: ntlmMessage = sspiJNIClient
2076: .invokePrepareSSOSubmit(ntlmMessage);
2077: } catch (Exception e) {
2078: throw new IOException("SSO Failed: " + e.getMessage());
2079: }
2080: out.write(ntlmMessage);
2081: } else {
2082: // host and domain name are _narrow_ strings.
2083: //byte[] domainBytes = domain.getBytes("UTF8");
2084: //byte[] user = user.getBytes("UTF8");
2085:
2086: byte[] lmAnswer, ntAnswer;
2087: //the response to the challenge...
2088:
2089: if (connection.getUseNTLMv2()) {
2090: //TODO: does this need to be random?
2091: //byte[] clientNonce = new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 };
2092: byte[] clientNonce = new byte[8];
2093: (new Random()).nextBytes(clientNonce);
2094:
2095: lmAnswer = NtlmAuth.answerLmv2Challenge(domain, user,
2096: password, nonce, clientNonce);
2097: ntAnswer = NtlmAuth.answerNtlmv2Challenge(domain, user,
2098: password, nonce, currentToken.ntlmTarget,
2099: clientNonce);
2100: } else {
2101: //LM/NTLM (v1)
2102: lmAnswer = NtlmAuth.answerLmChallenge(password, nonce);
2103: ntAnswer = NtlmAuth.answerNtChallenge(password, nonce);
2104: }
2105:
2106: final byte[] header = { 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53,
2107: 0x50, 0x00 };
2108: out.write(header); //header is ascii "NTLMSSP\0"
2109: out.write((int) 3); //sequence number = 3
2110: final int domainLenInBytes = domain.length() * 2;
2111: final int userLenInBytes = user.length() * 2;
2112: //mdb: not sending hostname; I hope this is ok!
2113: final int hostLenInBytes = 0; //localhostname.length()*2;
2114: int pos = 64 + domainLenInBytes + userLenInBytes
2115: + hostLenInBytes;
2116: // lan man response: length and offset
2117: out.write((short) lmAnswer.length);
2118: out.write((short) lmAnswer.length);
2119: out.write((int) pos);
2120: pos += lmAnswer.length;
2121: // nt response: length and offset
2122: out.write((short) ntAnswer.length);
2123: out.write((short) ntAnswer.length);
2124: out.write((int) pos);
2125: pos = 64;
2126: //domain
2127: out.write((short) domainLenInBytes);
2128: out.write((short) domainLenInBytes);
2129: out.write((int) pos);
2130: pos += domainLenInBytes;
2131:
2132: //user
2133: out.write((short) userLenInBytes);
2134: out.write((short) userLenInBytes);
2135: out.write((int) pos);
2136: pos += userLenInBytes;
2137: //local hostname
2138: out.write((short) hostLenInBytes);
2139: out.write((short) hostLenInBytes);
2140: out.write((int) pos);
2141: pos += hostLenInBytes;
2142: //unknown
2143: out.write((short) 0);
2144: out.write((short) 0);
2145: out.write((int) pos);
2146: //flags
2147: if (connection.getUseNTLMv2())
2148: out.write((int) 0x88201);
2149: else
2150: out.write((int) 0x8201);
2151: //variable length stuff...
2152: out.write(domain);
2153: out.write(user);
2154: //Not sending hostname...I hope this is OK!
2155: //comm.appendChars(localhostname);
2156:
2157: //the response to the challenge...
2158: out.write(lmAnswer);
2159: out.write(ntAnswer);
2160: }
2161: out.flush();
2162: }
2163:
2164: /**
2165: * Read the next TDS token from the response stream.
2166: *
2167: * @throws SQLException if an I/O or protocol error occurs
2168: */
2169: private void nextToken() throws SQLException {
2170: checkOpen();
2171: if (endOfResponse) {
2172: currentToken.token = TDS_DONE_TOKEN;
2173: currentToken.status = 0;
2174: return;
2175: }
2176: try {
2177: currentToken.token = (byte) in.read();
2178: switch (currentToken.token) {
2179: case TDS5_PARAMFMT2_TOKEN:
2180: tds5ParamFmt2Token();
2181: break;
2182: case TDS_LANG_TOKEN:
2183: tdsInvalidToken();
2184: break;
2185: case TDS5_WIDE_RESULT:
2186: tds5WideResultToken();
2187: break;
2188: case TDS_CLOSE_TOKEN:
2189: tdsInvalidToken();
2190: break;
2191: case TDS_RETURNSTATUS_TOKEN:
2192: tdsReturnStatusToken();
2193: break;
2194: case TDS_PROCID:
2195: tdsProcIdToken();
2196: break;
2197: case TDS_OFFSETS_TOKEN:
2198: tdsOffsetsToken();
2199: break;
2200: case TDS7_RESULT_TOKEN:
2201: tds7ResultToken();
2202: break;
2203: case TDS7_COMP_RESULT_TOKEN:
2204: tdsInvalidToken();
2205: break;
2206: case TDS_COLNAME_TOKEN:
2207: tds4ColNamesToken();
2208: break;
2209: case TDS_COLFMT_TOKEN:
2210: tds4ColFormatToken();
2211: break;
2212: case TDS_TABNAME_TOKEN:
2213: tdsTableNameToken();
2214: break;
2215: case TDS_COLINFO_TOKEN:
2216: tdsColumnInfoToken();
2217: break;
2218: case TDS_COMP_NAMES_TOKEN:
2219: tdsInvalidToken();
2220: break;
2221: case TDS_COMP_RESULT_TOKEN:
2222: tdsInvalidToken();
2223: break;
2224: case TDS_ORDER_TOKEN:
2225: tdsOrderByToken();
2226: break;
2227: case TDS_ERROR_TOKEN:
2228: case TDS_INFO_TOKEN:
2229: tdsErrorToken();
2230: break;
2231: case TDS_PARAM_TOKEN:
2232: tdsOutputParamToken();
2233: break;
2234: case TDS_LOGINACK_TOKEN:
2235: tdsLoginAckToken();
2236: break;
2237: case TDS_CONTROL_TOKEN:
2238: tdsControlToken();
2239: break;
2240: case TDS_ROW_TOKEN:
2241: tdsRowToken();
2242: break;
2243: case TDS_ALTROW:
2244: tdsInvalidToken();
2245: break;
2246: case TDS5_PARAMS_TOKEN:
2247: tds5ParamsToken();
2248: break;
2249: case TDS_CAP_TOKEN:
2250: tdsCapabilityToken();
2251: break;
2252: case TDS_ENVCHANGE_TOKEN:
2253: tdsEnvChangeToken();
2254: break;
2255: case TDS_MSG50_TOKEN:
2256: tds5ErrorToken();
2257: break;
2258: case TDS5_DYNAMIC_TOKEN:
2259: tds5DynamicToken();
2260: break;
2261: case TDS5_PARAMFMT_TOKEN:
2262: tds5ParamFmtToken();
2263: break;
2264: case TDS_AUTH_TOKEN:
2265: tdsNtlmAuthToken();
2266: break;
2267: case TDS_RESULT_TOKEN:
2268: tds5ResultToken();
2269: break;
2270: case TDS_DONE_TOKEN:
2271: case TDS_DONEPROC_TOKEN:
2272: case TDS_DONEINPROC_TOKEN:
2273: tdsDoneToken();
2274: break;
2275: default:
2276: throw new ProtocolException(
2277: "Invalid packet type 0x"
2278: + Integer
2279: .toHexString((int) currentToken.token & 0xFF));
2280: }
2281: } catch (IOException ioe) {
2282: connection.setClosed();
2283: throw Support.linkException(
2284: new SQLException(Messages.get(
2285: "error.generic.ioerror", ioe.getMessage()),
2286: "08S01"), ioe);
2287: } catch (ProtocolException pe) {
2288: connection.setClosed();
2289: throw Support.linkException(
2290: new SQLException(Messages.get(
2291: "error.generic.tdserror", pe.getMessage()),
2292: "08S01"), pe);
2293: } catch (OutOfMemoryError err) {
2294: // Consume the rest of the response
2295: in.skipToEnd();
2296: endOfResponse = true;
2297: endOfResults = true;
2298: cancelPending = false;
2299: throw err;
2300: }
2301: }
2302:
2303: /**
2304: * Report unsupported TDS token in input stream.
2305: *
2306: * @throws IOException
2307: */
2308: private void tdsInvalidToken() throws IOException,
2309: ProtocolException {
2310: in.skip(in.readShort());
2311: throw new ProtocolException("Unsupported TDS token: 0x"
2312: + Integer.toHexString((int) currentToken.token & 0xFF));
2313: }
2314:
2315: /**
2316: * Process TDS 5 Sybase 12+ Dynamic results parameter descriptor.
2317: * <p>When returning output parameters this token will be followed
2318: * by a TDS5_PARAMS_TOKEN with the actual data.
2319: * @throws IOException
2320: * @throws ProtocolException
2321: */
2322: private void tds5ParamFmt2Token() throws IOException,
2323: ProtocolException {
2324: in.readInt(); // Packet length
2325: int paramCnt = in.readShort();
2326: ColInfo[] params = new ColInfo[paramCnt];
2327: for (int i = 0; i < paramCnt; i++) {
2328: //
2329: // Get the parameter details using the
2330: // ColInfo class as the server format is the same.
2331: //
2332: ColInfo col = new ColInfo();
2333: int colNameLen = in.read();
2334: col.realName = in.readNonUnicodeString(colNameLen);
2335: int column_flags = in.readInt(); /* Flags */
2336: col.isCaseSensitive = false;
2337: col.nullable = ((column_flags & 0x20) != 0) ? ResultSetMetaData.columnNullable
2338: : ResultSetMetaData.columnNoNulls;
2339: col.isWriteable = (column_flags & 0x10) != 0;
2340: col.isIdentity = (column_flags & 0x40) != 0;
2341: col.isKey = (column_flags & 0x02) != 0;
2342: col.isHidden = (column_flags & 0x01) != 0;
2343:
2344: col.userType = in.readInt();
2345: TdsData.readType(in, col);
2346: // Skip locale information
2347: in.skip(1);
2348: params[i] = col;
2349: }
2350: currentToken.dynamParamInfo = params;
2351: currentToken.dynamParamData = new Object[paramCnt];
2352: }
2353:
2354: /**
2355: * Process Sybase 12+ wide result token which provides enhanced
2356: * column meta data.
2357: *
2358: * @throws IOException
2359: */
2360: private void tds5WideResultToken() throws IOException,
2361: ProtocolException {
2362: in.readInt(); // Packet length
2363: int colCnt = in.readShort();
2364: this .columns = new ColInfo[colCnt];
2365: this .rowData = new Object[colCnt];
2366: this .tables = null;
2367:
2368: for (int colNum = 0; colNum < colCnt; ++colNum) {
2369: ColInfo col = new ColInfo();
2370: //
2371: // Get the alias name
2372: //
2373: int nameLen = in.read();
2374: col.name = in.readNonUnicodeString(nameLen);
2375: //
2376: // Get the catalog name
2377: //
2378: nameLen = in.read();
2379: col.catalog = in.readNonUnicodeString(nameLen);
2380: //
2381: // Get the schema name
2382: //
2383: nameLen = in.read();
2384: col.schema = in.readNonUnicodeString(nameLen);
2385: //
2386: // Get the table name
2387: //
2388: nameLen = in.read();
2389: col.tableName = in.readNonUnicodeString(nameLen);
2390: //
2391: // Get the column name
2392: //
2393: nameLen = in.read();
2394: col.realName = in.readNonUnicodeString(nameLen);
2395: if (col.name == null || col.name.length() == 0) {
2396: col.name = col.realName;
2397: }
2398: int column_flags = in.readInt(); /* Flags */
2399: col.isCaseSensitive = false;
2400: col.nullable = ((column_flags & 0x20) != 0) ? ResultSetMetaData.columnNullable
2401: : ResultSetMetaData.columnNoNulls;
2402: col.isWriteable = (column_flags & 0x10) != 0;
2403: col.isIdentity = (column_flags & 0x40) != 0;
2404: col.isKey = (column_flags & 0x02) != 0;
2405: col.isHidden = (column_flags & 0x01) != 0;
2406:
2407: col.userType = in.readInt();
2408: TdsData.readType(in, col);
2409: // Skip locale information
2410: in.skip(1);
2411: columns[colNum] = col;
2412: }
2413: endOfResults = false;
2414: }
2415:
2416: /**
2417: * Process stored procedure return status token.
2418: *
2419: * @throws IOException
2420: */
2421: private void tdsReturnStatusToken() throws IOException,
2422: SQLException {
2423: returnStatus = new Integer(in.readInt());
2424: if (this .returnParam != null) {
2425: returnParam.setOutValue(Support.convert(this .connection,
2426: returnStatus, returnParam.jdbcType, connection
2427: .getCharset()));
2428: }
2429: }
2430:
2431: /**
2432: * Process procedure ID token.
2433: * <p>
2434: * Used by DBLIB to obtain the object id of a stored procedure.
2435: */
2436: private void tdsProcIdToken() throws IOException {
2437: in.skip(8);
2438: }
2439:
2440: /**
2441: * Process offsets token.
2442: * <p>
2443: * Used by DBLIB to return the offset of various keywords in a statement.
2444: * This saves the client from having to parse a SQL statement. Enabled with
2445: * <code>"set offsets from on"</code>.
2446: */
2447: private void tdsOffsetsToken() throws IOException {
2448: /*int keyword =*/in.read();
2449: /*int unknown =*/in.read();
2450: /*int offset =*/in.readShort();
2451: }
2452:
2453: /**
2454: * Process a TDS 7.0 result set token.
2455: *
2456: * @throws IOException
2457: * @throws ProtocolException
2458: */
2459: private void tds7ResultToken() throws IOException,
2460: ProtocolException, SQLException {
2461: endOfResults = false;
2462:
2463: int colCnt = in.readShort();
2464:
2465: if (colCnt < 0) {
2466: // Short packet returned by TDS8 when the column meta data is
2467: // supressed on cursor fetch etc.
2468: // NB. With TDS7 no result set packet is returned at all.
2469: return;
2470: }
2471:
2472: this .columns = new ColInfo[colCnt];
2473: this .rowData = new Object[colCnt];
2474: this .tables = null;
2475:
2476: for (int i = 0; i < colCnt; i++) {
2477: ColInfo col = new ColInfo();
2478:
2479: col.userType = in.readShort();
2480:
2481: int flags = in.readShort();
2482:
2483: col.nullable = ((flags & 0x01) != 0) ? ResultSetMetaData.columnNullable
2484: : ResultSetMetaData.columnNoNulls;
2485: col.isCaseSensitive = (flags & 0X02) != 0;
2486: col.isIdentity = (flags & 0x10) != 0;
2487: col.isWriteable = (flags & 0x0C) != 0;
2488: TdsData.readType(in, col);
2489: // Set the charsetInfo field of col
2490: if (tdsVersion >= Driver.TDS80 && col.collation != null) {
2491: TdsData.setColumnCharset(col, connection);
2492: }
2493:
2494: int clen = in.read();
2495:
2496: col.realName = in.readUnicodeString(clen);
2497: col.name = col.realName;
2498:
2499: this .columns[i] = col;
2500: }
2501: }
2502:
2503: /**
2504: * Process a TDS 4.2 column names token.
2505: * <p>
2506: * Note: Will be followed by a COL_FMT token.
2507: *
2508: * @throws IOException
2509: */
2510: private void tds4ColNamesToken() throws IOException {
2511: ArrayList colList = new ArrayList();
2512:
2513: final int pktLen = in.readShort();
2514: this .tables = null;
2515: int bytesRead = 0;
2516:
2517: while (bytesRead < pktLen) {
2518: ColInfo col = new ColInfo();
2519: int nameLen = in.read();
2520: String name = in.readNonUnicodeString(nameLen);
2521:
2522: bytesRead = bytesRead + 1 + nameLen;
2523: col.realName = name;
2524: col.name = name;
2525:
2526: colList.add(col);
2527: }
2528:
2529: int colCnt = colList.size();
2530: this .columns = (ColInfo[]) colList.toArray(new ColInfo[colCnt]);
2531: this .rowData = new Object[colCnt];
2532: }
2533:
2534: /**
2535: * Process a TDS 4.2 column format token.
2536: *
2537: * @throws IOException
2538: * @throws ProtocolException
2539: */
2540: private void tds4ColFormatToken() throws IOException,
2541: ProtocolException {
2542:
2543: final int pktLen = in.readShort();
2544:
2545: int bytesRead = 0;
2546: int numColumns = 0;
2547: while (bytesRead < pktLen) {
2548: if (numColumns > columns.length) {
2549: throw new ProtocolException(
2550: "Too many columns in TDS_COL_FMT packet");
2551: }
2552: ColInfo col = columns[numColumns];
2553:
2554: if (serverType == Driver.SQLSERVER) {
2555: col.userType = in.readShort();
2556:
2557: int flags = in.readShort();
2558:
2559: col.nullable = ((flags & 0x01) != 0) ? ResultSetMetaData.columnNullable
2560: : ResultSetMetaData.columnNoNulls;
2561: col.isCaseSensitive = (flags & 0x02) != 0;
2562: col.isWriteable = (flags & 0x0C) != 0;
2563: col.isIdentity = (flags & 0x10) != 0;
2564: } else {
2565: // Sybase does not send column flags
2566: col.isCaseSensitive = false;
2567: col.isWriteable = true;
2568:
2569: if (col.nullable == ResultSetMetaData.columnNoNulls) {
2570: col.nullable = ResultSetMetaData.columnNullableUnknown;
2571: }
2572:
2573: col.userType = in.readInt();
2574: }
2575: bytesRead += 4;
2576:
2577: bytesRead += TdsData.readType(in, col);
2578:
2579: numColumns++;
2580: }
2581:
2582: if (numColumns != columns.length) {
2583: throw new ProtocolException(
2584: "Too few columns in TDS_COL_FMT packet");
2585: }
2586:
2587: endOfResults = false;
2588: }
2589:
2590: /**
2591: * Process a table name token.
2592: * <p> Sent by select for browse or cursor functions.
2593: *
2594: * @throws IOException
2595: */
2596: private void tdsTableNameToken() throws IOException,
2597: ProtocolException {
2598: final int pktLen = in.readShort();
2599: int bytesRead = 0;
2600: ArrayList tableList = new ArrayList();
2601:
2602: while (bytesRead < pktLen) {
2603: int nameLen;
2604: String tabName;
2605: TableMetaData table;
2606: if (tdsVersion >= Driver.TDS81) {
2607: // TDS8.1 supplies the server.database.owner.table as up to
2608: // four separate components which allows us to have names
2609: // with embedded periods.
2610: table = new TableMetaData();
2611: bytesRead++;
2612: int tableNameToken = in.read();
2613: switch (tableNameToken) {
2614: case 4:
2615: nameLen = in.readShort();
2616: bytesRead += nameLen * 2 + 2;
2617: // Read and discard server name; see Bug 1403067
2618: in.readUnicodeString(nameLen);
2619: case 3:
2620: nameLen = in.readShort();
2621: bytesRead += nameLen * 2 + 2;
2622: table.catalog = in.readUnicodeString(nameLen);
2623: case 2:
2624: nameLen = in.readShort();
2625: bytesRead += nameLen * 2 + 2;
2626: table.schema = in.readUnicodeString(nameLen);
2627: case 1:
2628: nameLen = in.readShort();
2629: bytesRead += nameLen * 2 + 2;
2630: table.name = in.readUnicodeString(nameLen);
2631: case 0:
2632: break;
2633: default:
2634: throw new ProtocolException(
2635: "Invalid table TAB_NAME_TOKEN: "
2636: + tableNameToken);
2637: }
2638: } else {
2639: if (tdsVersion >= Driver.TDS70) {
2640: nameLen = in.readShort();
2641: bytesRead += nameLen * 2 + 2;
2642: tabName = in.readUnicodeString(nameLen);
2643: } else {
2644: // TDS 4.2 or TDS 5.0
2645: nameLen = in.read();
2646: bytesRead++;
2647: if (nameLen == 0) {
2648: continue; // Sybase/SQL 6.5 use a zero length name to terminate list
2649: }
2650: tabName = in.readNonUnicodeString(nameLen);
2651: bytesRead += nameLen;
2652: }
2653: table = new TableMetaData();
2654: // tabName can be a fully qualified name
2655: int dotPos = tabName.lastIndexOf('.');
2656: if (dotPos > 0) {
2657: table.name = tabName.substring(dotPos + 1);
2658:
2659: int nextPos = tabName.lastIndexOf('.', dotPos - 1);
2660: if (nextPos + 1 < dotPos) {
2661: table.schema = tabName.substring(nextPos + 1,
2662: dotPos);
2663: }
2664: dotPos = nextPos;
2665: nextPos = tabName.lastIndexOf('.', dotPos - 1);
2666: if (nextPos + 1 < dotPos) {
2667: table.catalog = tabName.substring(nextPos + 1,
2668: dotPos);
2669: }
2670: } else {
2671: table.name = tabName;
2672: }
2673: }
2674: tableList.add(table);
2675: }
2676: if (tableList.size() > 0) {
2677: this .tables = (TableMetaData[]) tableList
2678: .toArray(new TableMetaData[tableList.size()]);
2679: }
2680: }
2681:
2682: /**
2683: * Process a column infomation token.
2684: * <p>Sent by select for browse or cursor functions.
2685: * @throws IOException
2686: * @throws ProtocolException
2687: */
2688: private void tdsColumnInfoToken() throws IOException,
2689: ProtocolException {
2690: final int pktLen = in.readShort();
2691: int bytesRead = 0;
2692: int columnIndex = 0;
2693:
2694: while (bytesRead < pktLen) {
2695: // Seems like all columns are always returned in the COL_INFO
2696: // packet and there might be more than 255 columns, so we'll
2697: // just increment a counter instead.
2698: // Ignore the column index.
2699: in.read();
2700: if (columnIndex >= columns.length) {
2701: throw new ProtocolException("Column index "
2702: + (columnIndex + 1)
2703: + " invalid in TDS_COLINFO packet");
2704: }
2705: ColInfo col = columns[columnIndex++];
2706: int tableIndex = in.read();
2707: // In some cases (e.g. if the user calls 'CREATE CURSOR'), the
2708: // TDS_TABNAME packet seems to be missing although the table index
2709: // in this packet is > 0. Weird.
2710: // If tables are available check for valid table index.
2711: if (tables != null && tableIndex > tables.length) {
2712: throw new ProtocolException("Table index " + tableIndex
2713: + " invalid in TDS_COLINFO packet");
2714: }
2715: byte flags = (byte) in.read(); // flags
2716: bytesRead += 3;
2717:
2718: if (tableIndex != 0 && tables != null) {
2719: TableMetaData table = tables[tableIndex - 1];
2720: col.catalog = table.catalog;
2721: col.schema = table.schema;
2722: col.tableName = table.name;
2723: }
2724:
2725: col.isKey = (flags & 0x08) != 0;
2726: col.isHidden = (flags & 0x10) != 0;
2727:
2728: // If bit 5 is set, we have a column name
2729: if ((flags & 0x20) != 0) {
2730: final int nameLen = in.read();
2731: bytesRead += 1;
2732: final String colName = in.readString(nameLen);
2733: bytesRead += (tdsVersion >= Driver.TDS70) ? nameLen * 2
2734: : nameLen;
2735: col.realName = colName;
2736: }
2737: }
2738: }
2739:
2740: /**
2741: * Process an order by token.
2742: * <p>Sent to describe columns in an order by clause.
2743: * @throws IOException
2744: */
2745: private void tdsOrderByToken() throws IOException {
2746: // Skip this packet type
2747: int pktLen = in.readShort();
2748: in.skip(pktLen);
2749: }
2750:
2751: /**
2752: * Process a TD4/TDS7 error or informational message.
2753: *
2754: * @throws IOException
2755: */
2756: private void tdsErrorToken() throws IOException {
2757: int pktLen = in.readShort(); // Packet length
2758: int sizeSoFar = 6;
2759: int number = in.readInt();
2760: int state = in.read();
2761: int severity = in.read();
2762: int msgLen = in.readShort();
2763: String message = in.readString(msgLen);
2764: sizeSoFar += 2 + ((tdsVersion >= Driver.TDS70) ? msgLen * 2
2765: : msgLen);
2766: final int srvNameLen = in.read();
2767: String server = in.readString(srvNameLen);
2768: sizeSoFar += 1 + ((tdsVersion >= Driver.TDS70) ? srvNameLen * 2
2769: : srvNameLen);
2770:
2771: final int procNameLen = in.read();
2772: String procName = in.readString(procNameLen);
2773: sizeSoFar += 1 + ((tdsVersion >= Driver.TDS70) ? procNameLen * 2
2774: : procNameLen);
2775:
2776: int line = in.readShort();
2777: sizeSoFar += 2;
2778: // Skip any EED information to read rest of packet
2779: if (pktLen - sizeSoFar > 0)
2780: in.skip(pktLen - sizeSoFar);
2781:
2782: if (currentToken.token == TDS_ERROR_TOKEN) {
2783: if (severity < 10) {
2784: severity = 11; // Ensure treated as error
2785: }
2786: if (severity >= 20) {
2787: // A fatal error has occured, the connection will be closed by
2788: // the server immediately after the last TDS_DONE packet
2789: fatalError = true;
2790: }
2791: } else {
2792: if (severity > 9) {
2793: severity = 9; // Ensure treated as warning
2794: }
2795: }
2796: messages.addDiagnostic(number, state, severity, message,
2797: server, procName, line);
2798: }
2799:
2800: /**
2801: * Process output parameters.
2802: * </p>
2803: * Normally the output parameters are preceded by a TDS type 79
2804: * (procedure return value) record; however there are at least two
2805: * situations with TDS version 8 where this is not the case:
2806: * <ol>
2807: * <li>For the return value of a SQL 2000+ user defined function.</li>
2808: * <li>For a remote procedure call (server.database.user.procname) where
2809: * the 79 record is only sent if a result set is also returned by the remote
2810: * procedure. In this case the 79 record just acts as marker for the start of
2811: * the output parameters. The actual return value is in an output param token.</li>
2812: * </ol>
2813: * Output parameters are distinguished from procedure return values by the value of
2814: * a byte that immediately follows the parameter name. A value of 1 seems to indicate
2815: * a normal output parameter while a value of 2 indicates a procedure return value.
2816: *
2817: * @throws IOException
2818: * @throws ProtocolException
2819: */
2820: private void tdsOutputParamToken() throws IOException,
2821: ProtocolException, SQLException {
2822: in.readShort(); // Packet length
2823: String name = in.readString(in.read()); // Column Name
2824: // Next byte indicates if output parameter or return value
2825: // 1 = normal output param, 2 = function or stored proc return
2826: boolean funcReturnVal = (in.read() == 2);
2827: // Next byte is the parameter type that we supplied which
2828: // may not be the same as the parameter definition
2829: /* int inputTdsType = */in.read();
2830: // Not sure what these bytes are (they always seem to be zero).
2831: in.skip(3);
2832:
2833: ColInfo col = new ColInfo();
2834: TdsData.readType(in, col);
2835: // Set the charsetInfo field of col
2836: if (tdsVersion >= Driver.TDS80 && col.collation != null) {
2837: TdsData.setColumnCharset(col, connection);
2838: }
2839: Object value = TdsData.readData(connection, in, col);
2840:
2841: //
2842: // Real output parameters will either be unnamed or will have a valid
2843: // parameter name beginning with '@'. Ignore any other spurious parameters
2844: // such as those returned from calls to writetext in the proc.
2845: //
2846: if (parameters != null
2847: && (name.length() == 0 || name.startsWith("@"))) {
2848: if (tdsVersion >= Driver.TDS80 && funcReturnVal) {
2849: // TDS 8 Allows function return values of types other than int
2850: // Also used to for return value of remote procedure calls.
2851: if (returnParam != null) {
2852: if (value != null) {
2853: returnParam.setOutValue(Support.convert(
2854: connection, value,
2855: returnParam.jdbcType, connection
2856: .getCharset()));
2857: returnParam.collation = col.collation;
2858: returnParam.charsetInfo = col.charsetInfo;
2859: } else {
2860: returnParam.setOutValue(null);
2861: }
2862: }
2863: } else {
2864: // Look for next output parameter in list
2865: while (++nextParam < parameters.length) {
2866: if (parameters[nextParam].isOutput) {
2867: if (value != null) {
2868: parameters[nextParam]
2869: .setOutValue(Support
2870: .convert(
2871: connection,
2872: value,
2873: parameters[nextParam].jdbcType,
2874: connection
2875: .getCharset()));
2876: parameters[nextParam].collation = col.collation;
2877: parameters[nextParam].charsetInfo = col.charsetInfo;
2878: } else {
2879: parameters[nextParam].setOutValue(null);
2880: }
2881: break;
2882: }
2883: }
2884: }
2885: }
2886: }
2887:
2888: /**
2889: * Process a login acknowledgement packet.
2890: *
2891: * @throws IOException
2892: */
2893: private void tdsLoginAckToken() throws IOException {
2894: String product;
2895: int major, minor, build = 0;
2896: in.readShort(); // Packet length
2897:
2898: int ack = in.read(); // Ack TDS 5 = 5 for OK 6 for fail, 1/0 for the others
2899:
2900: // Update the TDS protocol version in this TdsCore and in the Socket.
2901: // The Connection will update itself immediately after this call.
2902: // As for other objects containing a TDS version value, there are none
2903: // at this point (we're just constructing the Connection).
2904: tdsVersion = TdsData.getTdsVersion(((int) in.read() << 24)
2905: | ((int) in.read() << 16) | ((int) in.read() << 8)
2906: | (int) in.read());
2907: socket.setTdsVersion(tdsVersion);
2908:
2909: product = in.readString(in.read());
2910:
2911: if (tdsVersion >= Driver.TDS70) {
2912: major = in.read();
2913: minor = in.read();
2914: build = in.read() << 8;
2915: build += in.read();
2916: } else {
2917: if (product.toLowerCase().startsWith("microsoft")) {
2918: in.skip(1);
2919: major = in.read();
2920: minor = in.read();
2921: } else {
2922: major = in.read();
2923: minor = in.read() * 10;
2924: minor += in.read();
2925: }
2926: in.skip(1);
2927: }
2928:
2929: if (product.length() > 1 && -1 != product.indexOf('\0')) {
2930: product = product.substring(0, product.indexOf('\0'));
2931: }
2932:
2933: connection.setDBServerInfo(product, major, minor, build);
2934:
2935: if (tdsVersion == Driver.TDS50 && ack != 5) {
2936: // Login rejected by server create SQLException
2937: messages.addDiagnostic(4002, 0, 14, "Login failed", "", "",
2938: 0);
2939: currentToken.token = TDS_ERROR_TOKEN;
2940: } else {
2941: // MJH 2005-11-02
2942: // If we get this far we are logged in OK so convert
2943: // any exceptions into warnings. Any exceptions are
2944: // likely to be caused by problems in accessing the
2945: // default database for this login id for SQL 6.5 and
2946: // Sybase ASE. SQL 7.0+ will fail to login if there is
2947: // no access to the default or specified database.
2948: // I am not convinced that this is a good idea but it
2949: // appears that other drivers e.g. jConnect do this and
2950: // return the exceptions on the connection warning chain.
2951: //
2952: SQLException ex = messages.exceptions;
2953: // Avoid returning useless warnings about language
2954: // character set etc.
2955: messages.clearWarnings();
2956: //
2957: // Convert exceptions to warnings
2958: //
2959: while (ex != null) {
2960: messages.addWarning(new SQLWarning(ex.getMessage(), ex
2961: .getSQLState(), ex.getErrorCode()));
2962: ex = ex.getNextException();
2963: }
2964: messages.exceptions = null;
2965: }
2966: }
2967:
2968: /**
2969: * Process a control token (function unknown).
2970: *
2971: * @throws IOException
2972: */
2973: private void tdsControlToken() throws IOException {
2974: int pktLen = in.readShort();
2975:
2976: in.skip(pktLen);
2977: }
2978:
2979: /**
2980: * Process a row data token.
2981: *
2982: * @throws IOException
2983: * @throws ProtocolException
2984: */
2985: private void tdsRowToken() throws IOException, ProtocolException {
2986: for (int i = 0; i < columns.length; i++) {
2987: rowData[i] = TdsData.readData(connection, in, columns[i]);
2988: }
2989:
2990: endOfResults = false;
2991: }
2992:
2993: /**
2994: * Process TDS 5.0 Params Token.
2995: * Stored procedure output parameters or data returned in parameter format
2996: * after a TDS Dynamic packet or as extended error information.
2997: * <p>The type of the preceding token is inspected to determine if this packet
2998: * contains output parameter result data. A TDS5_PARAMFMT2_TOKEN is sent before
2999: * this one in Sybase 12 to introduce output parameter results.
3000: * A TDS5_PARAMFMT_TOKEN is sent before this one to introduce extended error
3001: * information.
3002: *
3003: * @throws IOException
3004: */
3005: private void tds5ParamsToken() throws IOException,
3006: ProtocolException, SQLException {
3007: if (currentToken.dynamParamInfo == null) {
3008: throw new ProtocolException(
3009: "TDS 5 Param results token (0xD7) not preceded by param format (0xEC or 0X20).");
3010: }
3011:
3012: for (int i = 0; i < currentToken.dynamParamData.length; i++) {
3013: currentToken.dynamParamData[i] = TdsData.readData(
3014: connection, in, currentToken.dynamParamInfo[i]);
3015: String name = currentToken.dynamParamInfo[i].realName;
3016: //
3017: // Real output parameters will either be unnamed or will have a valid
3018: // parameter name beginning with '@'. Ignore any other Spurious parameters
3019: // such as those returned from calls to writetext in the proc.
3020: //
3021: if (parameters != null
3022: && (name.length() == 0 || name.startsWith("@"))) {
3023: // Sybase 12+ this token used to set output parameter results
3024: while (++nextParam < parameters.length) {
3025: if (parameters[nextParam].isOutput) {
3026: Object value = currentToken.dynamParamData[i];
3027: if (value != null) {
3028: parameters[nextParam]
3029: .setOutValue(Support
3030: .convert(
3031: connection,
3032: value,
3033: parameters[nextParam].jdbcType,
3034: connection
3035: .getCharset()));
3036: } else {
3037: parameters[nextParam].setOutValue(null);
3038: }
3039: break;
3040: }
3041: }
3042: }
3043: }
3044: }
3045:
3046: /**
3047: * Processes a TDS 5.0 capability token.
3048: * <p>
3049: * Sent after login to describe the server's capabilities.
3050: *
3051: * @throws IOException if an I/O error occurs
3052: */
3053: private void tdsCapabilityToken() throws IOException,
3054: ProtocolException {
3055: in.readShort(); // Packet length
3056: if (in.read() != 1) {
3057: throw new ProtocolException(
3058: "TDS_CAPABILITY: expected request string");
3059: }
3060: int capLen = in.read();
3061: if (capLen != 11 && capLen != 0) {
3062: throw new ProtocolException(
3063: "TDS_CAPABILITY: byte count not 11");
3064: }
3065: byte capRequest[] = new byte[11];
3066: if (capLen == 0) {
3067: Logger.println("TDS_CAPABILITY: Invalid request length");
3068: } else {
3069: in.read(capRequest);
3070: }
3071: if (in.read() != 2) {
3072: throw new ProtocolException(
3073: "TDS_CAPABILITY: expected response string");
3074: }
3075: capLen = in.read();
3076: if (capLen != 10 && capLen != 0) {
3077: throw new ProtocolException(
3078: "TDS_CAPABILITY: byte count not 10");
3079: }
3080: byte capResponse[] = new byte[10];
3081: if (capLen == 0) {
3082: Logger.println("TDS_CAPABILITY: Invalid response length");
3083: } else {
3084: in.read(capResponse);
3085: }
3086: //
3087: // Request capabilities
3088: //
3089: // jTDS sends 01 0B 4F FF 85 EE EF 65 7F FF FF FF D6
3090: // Sybase 11.92 01 0A 00 00 00 23 61 41 CF FF FF C6
3091: // Sybase 12.52 01 0A 03 84 0A E3 61 41 FF FF FF C6
3092: // Sybase 15.00 01 0B 4F F7 85 EA EB 61 7F FF FF FF C6
3093: //
3094: // Response capabilities
3095: //
3096: // jTDS sends 02 0A 00 00 04 06 80 06 48 00 00 00
3097: // Sybase 11.92 02 0A 00 00 00 00 00 06 00 00 00 00
3098: // Sybase 12.52 02 0A 00 00 00 00 00 06 00 00 00 00
3099: // Sybase 15.00 02 0A 00 00 04 00 00 06 00 00 00 00
3100: //
3101: // Now set the correct attributes for this connection.
3102: // See the CT_LIB documentation for details on the bit
3103: // positions.
3104: //
3105: int capMask = 0;
3106: if ((capRequest[0] & 0x02) == 0x02) {
3107: capMask |= SYB_UNITEXT;
3108: }
3109: if ((capRequest[1] & 0x03) == 0x03) {
3110: capMask |= SYB_DATETIME;
3111: }
3112: if ((capRequest[2] & 0x80) == 0x80) {
3113: capMask |= SYB_UNICODE;
3114: }
3115: if ((capRequest[3] & 0x02) == 0x02) {
3116: capMask |= SYB_EXTCOLINFO;
3117: }
3118: if ((capRequest[2] & 0x01) == 0x01) {
3119: capMask |= SYB_BIGINT;
3120: }
3121: if ((capRequest[4] & 0x04) == 0x04) {
3122: capMask |= SYB_BITNULL;
3123: }
3124: if ((capRequest[7] & 0x30) == 0x30) {
3125: capMask |= SYB_LONGDATA;
3126: }
3127: connection.setSybaseInfo(capMask);
3128: }
3129:
3130: /**
3131: * Process an environment change packet.
3132: *
3133: * @throws IOException
3134: * @throws SQLException
3135: */
3136: private void tdsEnvChangeToken() throws IOException, SQLException {
3137: int len = in.readShort();
3138: int type = in.read();
3139:
3140: switch (type) {
3141: case TDS_ENV_DATABASE: {
3142: int clen = in.read();
3143: final String newDb = in.readString(clen);
3144: clen = in.read();
3145: final String oldDb = in.readString(clen);
3146: connection.setDatabase(newDb, oldDb);
3147: break;
3148: }
3149:
3150: case TDS_ENV_LANG: {
3151: int clen = in.read();
3152: String language = in.readString(clen);
3153: clen = in.read();
3154: String oldLang = in.readString(clen);
3155: if (Logger.isActive()) {
3156: Logger.println("Language changed from " + oldLang
3157: + " to " + language);
3158: }
3159: break;
3160: }
3161:
3162: case TDS_ENV_CHARSET: {
3163: final int clen = in.read();
3164: final String charset = in.readString(clen);
3165: if (tdsVersion >= Driver.TDS70) {
3166: in.skip(len - 2 - clen * 2);
3167: } else {
3168: in.skip(len - 2 - clen);
3169: }
3170: connection.setServerCharset(charset);
3171: break;
3172: }
3173:
3174: case TDS_ENV_PACKSIZE: {
3175: final int blocksize;
3176: final int clen = in.read();
3177: blocksize = Integer.parseInt(in.readString(clen));
3178: if (tdsVersion >= Driver.TDS70) {
3179: in.skip(len - 2 - clen * 2);
3180: } else {
3181: in.skip(len - 2 - clen);
3182: }
3183: this .connection.setNetPacketSize(blocksize);
3184: out.setBufferSize(blocksize);
3185: if (Logger.isActive()) {
3186: Logger.println("Changed blocksize to " + blocksize);
3187: }
3188: }
3189: break;
3190:
3191: case TDS_ENV_LCID:
3192: // Only sent by TDS 7
3193: // In TDS 8 replaced by column specific collation info.
3194: // TODO Make use of this for character set conversions?
3195: in.skip(len - 1);
3196: break;
3197:
3198: case TDS_ENV_SQLCOLLATION: {
3199: int clen = in.read();
3200: byte collation[] = new byte[5];
3201: if (clen == 5) {
3202: in.read(collation);
3203: connection.setCollation(collation);
3204: } else {
3205: in.skip(clen);
3206: }
3207: clen = in.read();
3208: in.skip(clen);
3209: break;
3210: }
3211:
3212: default: {
3213: if (Logger.isActive()) {
3214: Logger.println("Unknown environment change type 0x"
3215: + Integer.toHexString(type));
3216: }
3217: in.skip(len - 1);
3218: break;
3219: }
3220: }
3221: }
3222:
3223: /**
3224: * Process a TDS 5 error or informational message.
3225: *
3226: * @throws IOException
3227: */
3228: private void tds5ErrorToken() throws IOException {
3229: int pktLen = in.readShort(); // Packet length
3230: int sizeSoFar = 6;
3231: int number = in.readInt();
3232: int state = in.read();
3233: int severity = in.read();
3234: // Discard text state
3235: int stateLen = in.read();
3236: in.readNonUnicodeString(stateLen);
3237: in.read(); // == 1 if extended error data follows
3238: // Discard status and transaction state
3239: in.readShort();
3240: sizeSoFar += 4 + stateLen;
3241:
3242: int msgLen = in.readShort();
3243: String message = in.readNonUnicodeString(msgLen);
3244: sizeSoFar += 2 + msgLen;
3245: final int srvNameLen = in.read();
3246: String server = in.readNonUnicodeString(srvNameLen);
3247: sizeSoFar += 1 + srvNameLen;
3248:
3249: final int procNameLen = in.read();
3250: String procName = in.readNonUnicodeString(procNameLen);
3251: sizeSoFar += 1 + procNameLen;
3252:
3253: int line = in.readShort();
3254: sizeSoFar += 2;
3255: // Skip any EED information to read rest of packet
3256: if (pktLen - sizeSoFar > 0)
3257: in.skip(pktLen - sizeSoFar);
3258:
3259: if (severity > 10) {
3260: messages.addDiagnostic(number, state, severity, message,
3261: server, procName, line);
3262: } else {
3263: messages.addDiagnostic(number, state, severity, message,
3264: server, procName, line);
3265: }
3266: }
3267:
3268: /**
3269: * Process TDS5 dynamic SQL aknowledgements.
3270: *
3271: * @throws IOException
3272: */
3273: private void tds5DynamicToken() throws IOException {
3274: int pktLen = in.readShort();
3275: byte type = (byte) in.read();
3276: /*byte status = (byte)*/in.read();
3277: pktLen -= 2;
3278: if (type == (byte) 0x20) {
3279: // Only handle aknowledgements for now
3280: int len = in.read();
3281: in.skip(len);
3282: pktLen -= len + 1;
3283: }
3284: in.skip(pktLen);
3285: }
3286:
3287: /**
3288: * Process TDS 5 Dynamic results parameter descriptors.
3289: * <p>
3290: * With Sybase 12+ this has been superseded by the TDS5_PARAMFMT2_TOKEN
3291: * except when used to return extended error information.
3292: *
3293: * @throws IOException
3294: * @throws ProtocolException
3295: */
3296: private void tds5ParamFmtToken() throws IOException,
3297: ProtocolException {
3298: in.readShort(); // Packet length
3299: int paramCnt = in.readShort();
3300: ColInfo[] params = new ColInfo[paramCnt];
3301: for (int i = 0; i < paramCnt; i++) {
3302: //
3303: // Get the parameter details using the
3304: // ColInfo class as the server format is the same.
3305: //
3306: ColInfo col = new ColInfo();
3307: int colNameLen = in.read();
3308: col.realName = in.readNonUnicodeString(colNameLen);
3309: int column_flags = in.read(); /* Flags */
3310: col.isCaseSensitive = false;
3311: col.nullable = ((column_flags & 0x20) != 0) ? ResultSetMetaData.columnNullable
3312: : ResultSetMetaData.columnNoNulls;
3313: col.isWriteable = (column_flags & 0x10) != 0;
3314: col.isIdentity = (column_flags & 0x40) != 0;
3315: col.isKey = (column_flags & 0x02) != 0;
3316: col.isHidden = (column_flags & 0x01) != 0;
3317:
3318: col.userType = in.readInt();
3319: if ((byte) in.peek() == TDS_DONE_TOKEN) {
3320: // Sybase 11.92 bug data type missing!
3321: currentToken.dynamParamInfo = null;
3322: currentToken.dynamParamData = null;
3323: // error trapped in sybasePrepare();
3324: messages.addDiagnostic(9999, 0, 16, "Prepare failed",
3325: "", "", 0);
3326:
3327: return; // Give up
3328: }
3329: TdsData.readType(in, col);
3330: // Skip locale information
3331: in.skip(1);
3332: params[i] = col;
3333: }
3334: currentToken.dynamParamInfo = params;
3335: currentToken.dynamParamData = new Object[paramCnt];
3336: }
3337:
3338: /**
3339: * Process a NTLM Authentication challenge.
3340: *
3341: * @throws IOException
3342: * @throws ProtocolException
3343: */
3344: private void tdsNtlmAuthToken() throws IOException,
3345: ProtocolException {
3346: int pktLen = in.readShort(); // Packet length
3347:
3348: int hdrLen = 40;
3349:
3350: if (pktLen < hdrLen)
3351: throw new ProtocolException(
3352: "NTLM challenge: packet is too small:" + pktLen);
3353:
3354: byte[] ntlmMessage = new byte[pktLen];
3355: in.read(ntlmMessage);
3356:
3357: final int seq = getIntFromBuffer(ntlmMessage, 8);
3358: if (seq != 2)
3359: throw new ProtocolException(
3360: "NTLM challenge: got unexpected sequence number:"
3361: + seq);
3362:
3363: final int flags = getIntFromBuffer(ntlmMessage, 20);
3364: //NOTE: the context is always included; if not local, then it is just
3365: // set to all zeros.
3366: //boolean hasContext = ((flags & 0x4000) != 0);
3367: //final boolean hasContext = true;
3368: //NOTE: even if target is omitted, the length will be zero.
3369: //final boolean hasTarget = ((flags & 0x800000) != 0);
3370:
3371: //extract the target, if present. This will be used for ntlmv2 auth.
3372: final int headerOffset = 40; // The assumes the context is always there, which appears to be the case.
3373: //header has: 2 byte lenght, 2 byte allocated space, and four-byte offset.
3374: int size = getShortFromBuffer(ntlmMessage, headerOffset);
3375: int offset = getIntFromBuffer(ntlmMessage, headerOffset + 4);
3376: currentToken.ntlmTarget = new byte[size];
3377: System.arraycopy(ntlmMessage, offset, currentToken.ntlmTarget,
3378: 0, size);
3379:
3380: currentToken.nonce = new byte[8];
3381: currentToken.ntlmMessage = ntlmMessage;
3382: System.arraycopy(ntlmMessage, 24, currentToken.nonce, 0, 8);
3383: }
3384:
3385: private static int getIntFromBuffer(byte[] buf, int offset) {
3386: int b1 = ((int) buf[offset] & 0xff);
3387: int b2 = ((int) buf[offset + 1] & 0xff) << 8;
3388: int b3 = ((int) buf[offset + 2] & 0xff) << 16;
3389: int b4 = ((int) buf[offset + 3] & 0xff) << 24;
3390: return b4 | b3 | b2 | b1;
3391: }
3392:
3393: private static int getShortFromBuffer(byte[] buf, int offset) {
3394: int b1 = ((int) buf[offset] & 0xff);
3395: int b2 = ((int) buf[offset + 1] & 0xff) << 8;
3396: return b2 | b1;
3397: }
3398:
3399: /**
3400: * Process a TDS 5.0 result set packet.
3401: *
3402: * @throws IOException
3403: * @throws ProtocolException
3404: */
3405: private void tds5ResultToken() throws IOException,
3406: ProtocolException {
3407: in.readShort(); // Packet length
3408: int colCnt = in.readShort();
3409: this .columns = new ColInfo[colCnt];
3410: this .rowData = new Object[colCnt];
3411: this .tables = null;
3412:
3413: for (int colNum = 0; colNum < colCnt; ++colNum) {
3414: //
3415: // Get the column name
3416: //
3417: ColInfo col = new ColInfo();
3418: int colNameLen = in.read();
3419: col.realName = in.readNonUnicodeString(colNameLen);
3420: col.name = col.realName;
3421: int column_flags = in.read(); /* Flags */
3422: col.isCaseSensitive = false;
3423: col.nullable = ((column_flags & 0x20) != 0) ? ResultSetMetaData.columnNullable
3424: : ResultSetMetaData.columnNoNulls;
3425: col.isWriteable = (column_flags & 0x10) != 0;
3426: col.isIdentity = (column_flags & 0x40) != 0;
3427: col.isKey = (column_flags & 0x02) != 0;
3428: col.isHidden = (column_flags & 0x01) != 0;
3429:
3430: col.userType = in.readInt();
3431: TdsData.readType(in, col);
3432: // Skip locale information
3433: in.skip(1);
3434: columns[colNum] = col;
3435: }
3436: endOfResults = false;
3437: }
3438:
3439: /**
3440: * Process a DONE, DONEINPROC or DONEPROC token.
3441: *
3442: * @throws IOException
3443: */
3444: private void tdsDoneToken() throws IOException {
3445: currentToken.status = (byte) in.read();
3446: in.skip(1);
3447: currentToken.operation = (byte) in.read();
3448: in.skip(1);
3449: currentToken.updateCount = in.readInt();
3450:
3451: if (!endOfResults) {
3452: // This will eliminate the select row count for sybase
3453: currentToken.status &= ~DONE_ROW_COUNT;
3454: endOfResults = true;
3455: }
3456:
3457: //
3458: // Check for cancel ack
3459: //
3460: if ((currentToken.status & DONE_CANCEL) != 0) {
3461: // Synchronize resetting of the cancelPending flag to ensure it
3462: // doesn't happen during the sending of a cancel request
3463: synchronized (cancelMonitor) {
3464: cancelPending = false;
3465: // Only throw an exception if this was a cancel() call
3466: if (cancelMonitor[0] == ASYNC_CANCEL) {
3467: messages.addException(new SQLException(
3468: Messages.get("error.generic.cancelled",
3469: "Statement"), "HY008"));
3470: }
3471: }
3472: }
3473:
3474: if ((currentToken.status & DONE_MORE_RESULTS) == 0) {
3475: //
3476: // There are no more results or pending cancel packets
3477: // to process.
3478: //
3479: endOfResponse = !cancelPending;
3480:
3481: if (fatalError) {
3482: // A fatal error has occured, the server has closed the
3483: // connection
3484: connection.setClosed();
3485: }
3486: }
3487:
3488: if (serverType == Driver.SQLSERVER) {
3489: //
3490: // MS SQL Server provides additional information we
3491: // can use to return special row counts for DDL etc.
3492: //
3493: if (currentToken.operation == (byte) 0xC1) {
3494: currentToken.status &= ~DONE_ROW_COUNT;
3495: }
3496: }
3497: }
3498:
3499: /**
3500: * Execute SQL using TDS 4.2 protocol.
3501: *
3502: * @param sql The SQL statement to execute.
3503: * @param procName Stored procedure to execute or null.
3504: * @param parameters Parameters for call or null.
3505: * @param noMetaData Suppress meta data for cursor calls.
3506: * @throws SQLException
3507: */
3508: private void executeSQL42(String sql, String procName,
3509: ParamInfo[] parameters, boolean noMetaData, boolean sendNow)
3510: throws IOException, SQLException {
3511: if (procName != null) {
3512: // RPC call
3513: out.setPacketType(RPC_PKT);
3514: byte[] buf = Support.encodeString(connection.getCharset(),
3515: procName);
3516:
3517: out.write((byte) buf.length);
3518: out.write(buf);
3519: out.write((short) (noMetaData ? 2 : 0));
3520:
3521: if (parameters != null) {
3522: for (int i = nextParam + 1; i < parameters.length; i++) {
3523: if (parameters[i].name != null) {
3524: buf = Support.encodeString(connection
3525: .getCharset(), parameters[i].name);
3526: out.write((byte) buf.length);
3527: out.write(buf);
3528: } else {
3529: out.write((byte) 0);
3530: }
3531:
3532: out.write((byte) (parameters[i].isOutput ? 1 : 0));
3533: TdsData.writeParam(out,
3534: connection.getCharsetInfo(), null,
3535: parameters[i]);
3536: }
3537: }
3538: if (!sendNow) {
3539: // Send end of packet byte to batch RPC
3540: out.write((byte) DONE_END_OF_RESPONSE);
3541: }
3542: } else if (sql.length() > 0) {
3543: if (parameters != null) {
3544: sql = Support.substituteParameters(sql, parameters,
3545: connection);
3546: }
3547:
3548: out.setPacketType(QUERY_PKT);
3549: out.write(sql);
3550: if (!sendNow) {
3551: // Batch SQL statements
3552: out.write(" ");
3553: }
3554: }
3555: }
3556:
3557: /**
3558: * Execute SQL using TDS 5.0 protocol.
3559: *
3560: * @param sql The SQL statement to execute.
3561: * @param procName Stored procedure to execute or null.
3562: * @param parameters Parameters for call or null.
3563: * @throws SQLException
3564: */
3565: private void executeSQL50(String sql, String procName,
3566: ParamInfo[] parameters) throws IOException, SQLException {
3567: boolean haveParams = parameters != null;
3568: boolean useParamNames = false;
3569: currentToken.dynamParamInfo = null;
3570: currentToken.dynamParamData = null;
3571: //
3572: // Sybase does not allow text or image parameters as parameters
3573: // to statements or stored procedures. With Sybase 12.5 it is
3574: // possible to use a new TDS data type to send long data as
3575: // parameters to statements (but not procedures). This usage
3576: // replaces the writetext command that had to be used in the past.
3577: // As we do not support writetext, with older versions of Sybase
3578: // we just give up and embed all text/image data in the SQL statement.
3579: //
3580: for (int i = 0; haveParams && i < parameters.length; i++) {
3581: if ("text".equals(parameters[i].sqlType)
3582: || "image".equals(parameters[i].sqlType)
3583: || "unitext".equals(parameters[i].sqlType)) {
3584: if (procName != null && procName.length() > 0) {
3585: // Call to store proc nothing we can do
3586: if ("text".equals(parameters[i].sqlType)
3587: || "unitext".equals(parameters[i].sqlType)) {
3588: throw new SQLException(Messages
3589: .get("error.chartoolong"), "HY000");
3590: }
3591:
3592: throw new SQLException(Messages
3593: .get("error.bintoolong"), "HY000");
3594: }
3595: if (parameters[i].tdsType != TdsData.SYBLONGDATA) {
3596: // prepared statement substitute parameters into SQL
3597: sql = Support.substituteParameters(sql, parameters,
3598: connection);
3599: haveParams = false;
3600: procName = null;
3601: break;
3602: }
3603: }
3604: }
3605:
3606: out.setPacketType(SYBQUERY_PKT);
3607:
3608: if (procName == null) {
3609: // Use TDS_LANGUAGE TOKEN with optional parameters
3610: out.write((byte) TDS_LANG_TOKEN);
3611:
3612: if (haveParams) {
3613: sql = Support.substituteParamMarkers(sql, parameters);
3614: }
3615:
3616: if (connection.isWideChar()) {
3617: // Need to preconvert string to get correct length
3618: byte[] buf = Support.encodeString(connection
3619: .getCharset(), sql);
3620:
3621: out.write((int) buf.length + 1);
3622: out.write((byte) (haveParams ? 1 : 0));
3623: out.write(buf);
3624: } else {
3625: out.write((int) sql.length() + 1);
3626: out.write((byte) (haveParams ? 1 : 0));
3627: out.write(sql);
3628: }
3629: } else if (procName.startsWith("#jtds")) {
3630: // Dynamic light weight procedure call
3631: out.write((byte) TDS5_DYNAMIC_TOKEN);
3632: out.write((short) (procName.length() + 4));
3633: out.write((byte) 2);
3634: out.write((byte) (haveParams ? 1 : 0));
3635: out.write((byte) (procName.length() - 1));
3636: out.write(procName.substring(1));
3637: out.write((short) 0);
3638: } else {
3639: byte buf[] = Support.encodeString(connection.getCharset(),
3640: procName);
3641:
3642: // RPC call
3643: out.write((byte) TDS_DBRPC_TOKEN);
3644: out.write((short) (buf.length + 3));
3645: out.write((byte) buf.length);
3646: out.write(buf);
3647: out.write((short) (haveParams ? 2 : 0));
3648: useParamNames = true;
3649: }
3650:
3651: //
3652: // Output any parameters
3653: //
3654: if (haveParams) {
3655: // First write parameter descriptors
3656: out.write((byte) TDS5_PARAMFMT_TOKEN);
3657:
3658: int len = 2;
3659:
3660: for (int i = nextParam + 1; i < parameters.length; i++) {
3661: len += TdsData.getTds5ParamSize(
3662: connection.getCharset(), connection
3663: .isWideChar(), parameters[i],
3664: useParamNames);
3665: }
3666:
3667: out.write((short) len);
3668: out.write((short) ((nextParam < 0) ? parameters.length
3669: : parameters.length - 1));
3670:
3671: for (int i = nextParam + 1; i < parameters.length; i++) {
3672: TdsData.writeTds5ParamFmt(out, connection.getCharset(),
3673: connection.isWideChar(), parameters[i],
3674: useParamNames);
3675: }
3676:
3677: // Now write the actual data
3678: out.write((byte) TDS5_PARAMS_TOKEN);
3679:
3680: for (int i = nextParam + 1; i < parameters.length; i++) {
3681: TdsData.writeTds5Param(out,
3682: connection.getCharsetInfo(), parameters[i]);
3683: }
3684: }
3685: }
3686:
3687: /**
3688: * Returns <code>true</code> if the specified <code>procName</code>
3689: * is a sp_prepare or sp_prepexec handle; returns <code>false</code>
3690: * otherwise.
3691: *
3692: * @param procName Stored procedure to execute or <code>null</code>.
3693: * @return <code>true</code> if the specified <code>procName</code>
3694: * is a sp_prepare or sp_prepexec handle; <code>false</code>
3695: * otherwise.
3696: */
3697: public static boolean isPreparedProcedureName(final String procName) {
3698: return procName != null && procName.length() > 0
3699: && Character.isDigit(procName.charAt(0));
3700: }
3701:
3702: /**
3703: * Execute SQL using TDS 7.0 protocol.
3704: *
3705: * @param sql The SQL statement to execute.
3706: * @param procName Stored procedure to execute or <code>null</code>.
3707: * @param parameters Parameters for call or <code>null</code>.
3708: * @param noMetaData Suppress meta data for cursor calls.
3709: * @throws SQLException
3710: */
3711: private void executeSQL70(String sql, String procName,
3712: ParamInfo[] parameters, boolean noMetaData, boolean sendNow)
3713: throws IOException, SQLException {
3714: int prepareSql = connection.getPrepareSql();
3715:
3716: if (parameters == null && prepareSql == EXECUTE_SQL) {
3717: // Downgrade EXECUTE_SQL to UNPREPARED
3718: // if there are no parameters.
3719: //
3720: // Should we downgrade TEMPORARY_STORED_PROCEDURES and PREPARE as well?
3721: // No it may be a complex select with no parameters but costly to
3722: // evaluate for each execution.
3723: prepareSql = UNPREPARED;
3724: }
3725:
3726: if (inBatch) {
3727: // For batch execution with parameters
3728: // we need to be consistant and use
3729: // execute SQL
3730: prepareSql = EXECUTE_SQL;
3731: }
3732:
3733: if (procName == null) {
3734: // No procedure name so not a callable statement and also
3735: // not a temporary stored procedure call.
3736: if (parameters != null) {
3737: if (prepareSql == TdsCore.UNPREPARED) {
3738: // Low tech approach just substitute parameter data into the
3739: // SQL statement.
3740: sql = Support.substituteParameters(sql, parameters,
3741: connection);
3742: } else {
3743: // If we have parameters then we need to use sp_executesql to
3744: // parameterise the statement unless the user has specified
3745: ParamInfo[] params;
3746:
3747: params = new ParamInfo[2 + parameters.length];
3748: System.arraycopy(parameters, 0, params, 2,
3749: parameters.length);
3750:
3751: params[0] = new ParamInfo(Types.LONGVARCHAR,
3752: Support.substituteParamMarkers(sql,
3753: parameters), ParamInfo.UNICODE);
3754: TdsData.getNativeType(connection, params[0]);
3755:
3756: params[1] = new ParamInfo(
3757: Types.LONGVARCHAR,
3758: Support.getParameterDefinitions(parameters),
3759: ParamInfo.UNICODE);
3760: TdsData.getNativeType(connection, params[1]);
3761:
3762: parameters = params;
3763:
3764: // Use sp_executesql approach
3765: procName = "sp_executesql";
3766: }
3767: }
3768: } else {
3769: // Either a stored procedure name has been supplied or this
3770: // statement should executed using a prepared statement handle
3771: if (isPreparedProcedureName(procName)) {
3772: // If the procedure is a prepared handle then redefine the
3773: // procedure name as sp_execute with the handle as a parameter.
3774: ParamInfo params[];
3775:
3776: if (parameters != null) {
3777: params = new ParamInfo[1 + parameters.length];
3778: System.arraycopy(parameters, 0, params, 1,
3779: parameters.length);
3780: } else {
3781: params = new ParamInfo[1];
3782: }
3783:
3784: params[0] = new ParamInfo(Types.INTEGER, new Integer(
3785: procName), ParamInfo.INPUT);
3786: TdsData.getNativeType(connection, params[0]);
3787:
3788: parameters = params;
3789:
3790: // Use sp_execute approach
3791: procName = "sp_execute";
3792: }
3793: }
3794:
3795: if (procName != null) {
3796: // RPC call
3797: out.setPacketType(RPC_PKT);
3798: Integer shortcut;
3799:
3800: if (tdsVersion >= Driver.TDS80
3801: && (shortcut = (Integer) tds8SpNames.get(procName)) != null) {
3802: // Use the shortcut form of procedure name for TDS8
3803: out.write((short) -1);
3804: out.write((short) shortcut.shortValue());
3805: } else {
3806: out.write((short) procName.length());
3807: out.write(procName);
3808: }
3809: //
3810: // If noMetaData is true then column meta data will be supressed.
3811: // This option is used by sp_cursorfetch or optionally by sp_execute
3812: // provided that the required meta data has been cached.
3813: //
3814: out.write((short) (noMetaData ? 2 : 0));
3815:
3816: if (parameters != null) {
3817: // Send the required parameter data
3818: for (int i = nextParam + 1; i < parameters.length; i++) {
3819: if (parameters[i].name != null) {
3820: out.write((byte) parameters[i].name.length());
3821: out.write(parameters[i].name);
3822: } else {
3823: out.write((byte) 0);
3824: }
3825:
3826: out.write((byte) (parameters[i].isOutput ? 1 : 0));
3827:
3828: TdsData.writeParam(out,
3829: connection.getCharsetInfo(), connection
3830: .getCollation(), parameters[i]);
3831: }
3832: }
3833: if (!sendNow) {
3834: // Append RPC packets
3835: out.write((byte) DONE_END_OF_RESPONSE);
3836: }
3837: } else if (sql.length() > 0) {
3838: // Simple SQL query with no parameters
3839: out.setPacketType(QUERY_PKT);
3840: out.write(sql);
3841: if (!sendNow) {
3842: // Append SQL packets
3843: out.write(" ");
3844: }
3845: }
3846: }
3847:
3848: /**
3849: * Sets the server row count (to limit the number of rows in a result set)
3850: * and text size (to limit the size of returned TEXT/NTEXT fields).
3851: *
3852: * @param rowCount the number of rows to return or 0 for no limit or -1 to
3853: * leave as is
3854: * @param textSize the maximum number of bytes in a TEXT column to return
3855: * or -1 to leave as is
3856: * @throws SQLException if an error is returned by the server
3857: */
3858: private void setRowCountAndTextSize(int rowCount, int textSize)
3859: throws SQLException {
3860: boolean newRowCount = rowCount >= 0
3861: && rowCount != connection.getRowCount();
3862: boolean newTextSize = textSize >= 0
3863: && textSize != connection.getTextSize();
3864: if (newRowCount || newTextSize) {
3865: try {
3866: StringBuffer query = new StringBuffer(64);
3867: if (newRowCount) {
3868: query.append("SET ROWCOUNT ").append(rowCount);
3869: }
3870: if (newTextSize) {
3871: query.append(" SET TEXTSIZE ").append(
3872: textSize == 0 ? 2147483647 : textSize);
3873: }
3874: out.setPacketType(QUERY_PKT);
3875: out.write(query.toString());
3876: out.flush();
3877: endOfResponse = false;
3878: endOfResults = true;
3879: wait(0);
3880: clearResponseQueue();
3881: messages.checkErrors();
3882: // Update the values stored in the Connection
3883: connection.setRowCount(rowCount);
3884: connection.setTextSize(textSize);
3885: } catch (IOException ioe) {
3886: throw new SQLException(Messages.get(
3887: "error.generic.ioerror", ioe.getMessage()),
3888: "08S01");
3889: }
3890: }
3891: }
3892:
3893: /**
3894: * Waits for the first byte of the server response.
3895: *
3896: * @param timeOut the timeout period in seconds or 0
3897: */
3898: private void wait(int timeOut) throws IOException, SQLException {
3899: Object timer = null;
3900: try {
3901: if (timeOut > 0) {
3902: // Start a query timeout timer
3903: timer = TimerThread.getInstance().setTimer(
3904: timeOut * 1000,
3905: new TimerThread.TimerListener() {
3906: public void timerExpired() {
3907: TdsCore.this .cancel(true);
3908: }
3909: });
3910: }
3911: in.peek();
3912: } finally {
3913: if (timer != null) {
3914: if (!TimerThread.getInstance().cancelTimer(timer)) {
3915: throw new SQLException(Messages
3916: .get("error.generic.timeout"), "HYT00");
3917: }
3918: }
3919: }
3920: }
3921:
3922: /**
3923: * Releases parameter and result set data and metadata to free up memory.
3924: * <p/>
3925: * This is useful before the <code>TdsCore</code> is cached for reuse.
3926: */
3927: public void cleanUp() {
3928: if (endOfResponse) {
3929: // Clean up parameters
3930: returnParam = null;
3931: parameters = null;
3932: // Clean up result data and meta data
3933: columns = null;
3934: rowData = null;
3935: tables = null;
3936: // Clean up warnings; any exceptions will be cleared when thrown
3937: messages.clearWarnings();
3938: }
3939: }
3940:
3941: /**
3942: * Returns the diagnostic chain for this instance.
3943: */
3944: public SQLDiagnostic getMessages() {
3945: return messages;
3946: }
3947:
3948: /**
3949: * Converts a user supplied MAC address into a byte array.
3950: *
3951: * @param macString the MAC address as a hex string
3952: * @return the MAC address as a <code>byte[]</code>
3953: */
3954: private static byte[] getMACAddress(String macString) {
3955: byte[] mac = new byte[6];
3956: boolean ok = false;
3957:
3958: if (macString != null && macString.length() == 12) {
3959: try {
3960: for (int i = 0, j = 0; i < 6; i++, j += 2) {
3961: mac[i] = (byte) Integer.parseInt(macString
3962: .substring(j, j + 2), 16);
3963: }
3964:
3965: ok = true;
3966: } catch (Exception ex) {
3967: // Ignore it. ok will be false.
3968: }
3969: }
3970:
3971: if (!ok) {
3972: Arrays.fill(mac, (byte) 0);
3973: }
3974:
3975: return mac;
3976: }
3977:
3978: /**
3979: * Tries to figure out what client name we should identify ourselves as.
3980: * Gets the hostname of this machine,
3981: *
3982: * @return name to use as the client
3983: */
3984: private static String getHostName() {
3985: if (hostName != null) {
3986: return hostName;
3987: }
3988:
3989: String name;
3990:
3991: try {
3992: name = java.net.InetAddress.getLocalHost().getHostName()
3993: .toUpperCase();
3994: } catch (java.net.UnknownHostException e) {
3995: hostName = "UNKNOWN";
3996: return hostName;
3997: }
3998:
3999: int pos = name.indexOf('.');
4000:
4001: if (pos >= 0) {
4002: name = name.substring(0, pos);
4003: }
4004:
4005: if (name.length() == 0) {
4006: hostName = "UNKNOWN";
4007: return hostName;
4008: }
4009:
4010: try {
4011: Integer.parseInt(name);
4012: // All numbers probably an IP address
4013: hostName = "UNKNOWN";
4014: return hostName;
4015: } catch (NumberFormatException e) {
4016: // Bit tacky but simple check for all numbers
4017: }
4018:
4019: hostName = name;
4020: return name;
4021: }
4022:
4023: /**
4024: * A <B>very</B> poor man's "encryption".
4025: *
4026: * @param pw password to encrypt
4027: * @return encrypted password
4028: */
4029: private static String tds7CryptPass(final String pw) {
4030: final int xormask = 0x5A5A;
4031: final int len = pw.length();
4032: final char[] chars = new char[len];
4033:
4034: for (int i = 0; i < len; ++i) {
4035: final int c = (int) (pw.charAt(i)) ^ xormask;
4036: final int m1 = (c >> 4) & 0x0F0F;
4037: final int m2 = (c << 4) & 0xF0F0;
4038:
4039: chars[i] = (char) (m1 | m2);
4040: }
4041:
4042: return new String(chars);
4043: }
4044: }
|