a451ba2006-02-09Henrik Grubbström (Grubba) /* * A Pike implementation of the TDS protocol. * * Henrik Grubbström 2006-02-08. */
c6015f2007-01-01Martin Nilsson #pike __REAL_VERSION__
48b40f2007-04-13Henrik Grubbström (Grubba) /* #define TDS_DEBUG */ /* #define TDS_CONVERT_DEBUG */
a451ba2006-02-09Henrik Grubbström (Grubba)  #ifdef TDS_DEBUG #define TDS_WERROR(X...) werror("TDS:" + X) #else #define TDS_WERROR(X...) #endif
5495b42006-02-13Henrik Grubbström (Grubba) #ifdef TDS_CONVERT_DEBUG #define TDS_CONV_WERROR(X...) werror("TDS: Convert: " + X) #else #define TDS_CONV_WERROR(X...) #endif
a451ba2006-02-09Henrik Grubbström (Grubba) 
1084be2006-02-14Henrik Grubbström (Grubba) //! //! The TDS SQL-protocol. //! //! This protocol is used by Sybase and Microsoft's SQL-servers. //! //! @example //! @tt{Sql.Sql con = Sql.Sql("tds://user:pass@@host/database");@} //! //! @seealso //! @[Sql.Sql()]
9eaf1d2008-06-28Martin Nilsson protected int filter_noprint(int char)
a451ba2006-02-09Henrik Grubbström (Grubba) { return ((char == 32) || ((char != 0x7f) && (char & 0x60)))?char:'.'; }
9eaf1d2008-06-28Martin Nilsson protected string hex_dump(string data) {
a451ba2006-02-09Henrik Grubbström (Grubba)  array(string) lines = data/16.0; int off; foreach(lines; int i; string line) { array(array(int)) halves = (array(array(int)))((line + ("\0"*16))/8); lines[i] = sprintf("%04x:%{ %02x%}:%{%02x %} %s:%s\n", off, halves[0], halves[1], (string)map(halves[0], filter_noprint), (string)map(halves[1], filter_noprint)); off += 16; } return lines * ""; }
26d74c2006-02-10Henrik Grubbström (Grubba) #if (__REAL_MAJOR__ > 7) || ((__REAL_MAJOR__ == 7) && (__REAL_MINOR__ >= 6)) // Static blocks affect nested classes in Pike 7.4. // We don't want that...
9eaf1d2008-06-28Martin Nilsson protected {
26d74c2006-02-10Henrik Grubbström (Grubba) #endif /* Pike 7.6 or later */
a451ba2006-02-09Henrik Grubbström (Grubba)  constant DEF_MAJOR = 8; constant DEF_MINOR = 0; constant DEF_PORT = 1433; constant TDS8VERSION = "\x01\x00\x00\x71"; constant CLIENTVERSION = "\x06\x83\xf2\xf8"; constant CONNECTIONID = "\0\0\0\0"; constant TIMEZONE = "\x88\xff\xff\xff"; constant COLLATION = "\x36\x04\x00\x00";
1084be2006-02-14Henrik Grubbström (Grubba)  //! Tokens that may occur in the answers from the server.
a451ba2006-02-09Henrik Grubbström (Grubba)  enum Token { TDS_ERROR = 3, TDS_DONT_RETURN = 42, TDS5_PARAMFMT2_TOKEN = 32, /* 0x20 */ TDS_LANGUAGE_TOKEN = 33, /* 0x21 TDS 5.0 only */ TDS_ORDERBY2_TOKEN = 34, /* 0x22 */ TDS_ROWFMT2_TOKEN = 97, /* 0x61 TDS 5.0 only */ TDS_LOGOUT_TOKEN = 113, /* 0x71 TDS 5.0 only? ct_close() */ TDS_RETURNSTATUS_TOKEN = 121, /* 0x79 */ TDS_PROCID_TOKEN = 124, /* 0x7C TDS 4.2 only - TDS_PROCID */ TDS7_RESULT_TOKEN = 129, /* 0x81 TDS 7.0 only */ TDS7_COMPUTE_RESULT_TOKEN = 136, /* 0x88 TDS 7.0 only */ TDS_COLNAME_TOKEN = 160, /* 0xA0 TDS 4.2 only */ TDS_COLFMT_TOKEN = 161, /* 0xA1 TDS 4.2 only - TDS_COLFMT */ TDS_DYNAMIC2_TOKEN = 163, /* 0xA3 */ TDS_TABNAME_TOKEN = 164, /* 0xA4 */ TDS_COLINFO_TOKEN = 165, /* 0xA5 */ TDS_OPTIONCMD_TOKEN = 166, /* 0xA6 */ TDS_COMPUTE_NAMES_TOKEN = 167, /* 0xA7 */ TDS_COMPUTE_RESULT_TOKEN = 168, /* 0xA8 */ TDS_ORDERBY_TOKEN = 169, /* 0xA9 TDS_ORDER */ TDS_ERROR_TOKEN = 170, /* 0xAA */ TDS_INFO_TOKEN = 171, /* 0xAB */ TDS_PARAM_TOKEN = 172, /* 0xAC RETURNVALUE? */ TDS_LOGINACK_TOKEN = 173, /* 0xAD */ TDS_CONTROL_TOKEN = 174, /* 0xAE TDS_CONTROL */ TDS_ROW_TOKEN = 209, /* 0xD1 */ TDS_CMP_ROW_TOKEN = 211, /* 0xD3 */ TDS5_PARAMS_TOKEN = 215, /* 0xD7 TDS 5.0 only */ TDS_CAPABILITY_TOKEN = 226, /* 0xE2 */ TDS_ENVCHANGE_TOKEN = 227, /* 0xE3 */ TDS_EED_TOKEN = 229, /* 0xE5 */ TDS_DBRPC_TOKEN = 230, /* 0xE6 */ TDS5_DYNAMIC_TOKEN = 231, /* 0xE7 TDS 5.0 only */ TDS5_PARAMFMT_TOKEN = 236, /* 0xEC TDS 5.0 only */ TDS_AUTH_TOKEN = 237, /* 0xED */ TDS_RESULT_TOKEN = 238, /* 0xEE */ TDS_DONE_TOKEN = 253, /* 0xFD TDS_DONE */ TDS_DONEPROC_TOKEN = 254, /* 0xFE TDS_DONEPROC */ TDS_DONEINPROC_TOKEN = 255, /* 0xFF TDS_DONEINPROC */ /* CURSOR support: TDS 5.0 only*/ TDS_CURCLOSE_TOKEN = 128, /* 0x80 TDS 5.0 only */ TDS_CURFETCH_TOKEN = 130, /* 0x82 TDS 5.0 only */ TDS_CURINFO_TOKEN = 131, /* 0x83 TDS 5.0 only */ TDS_CUROPEN_TOKEN = 132, /* 0x84 TDS 5.0 only */ TDS_CURDECLARE_TOKEN = 134, /* 0x86 TDS 5.0 only */ };
1084be2006-02-14Henrik Grubbström (Grubba)  //! Environment types. //! //! Used by @[TDS_ENV_CHANGE_TOKEN]
a451ba2006-02-09Henrik Grubbström (Grubba)  enum EnvType { /* environment type field */ TDS_ENV_DATABASE = 1, TDS_ENV_LANG = 2, TDS_ENV_CHARSET = 3, TDS_ENV_PACKSIZE = 4, TDS_ENV_LCID = 5, TDS_ENV_SQLCOLLATION = 7, };
1084be2006-02-14Henrik Grubbström (Grubba)  //! Field types. enum FieldType {
a451ba2006-02-09Henrik Grubbström (Grubba)  SYBBINARY = 45, /* 0x2d */ SYBBIT = 50, /* 0x32 */ SYBBITN = 104, /* 0x68 */ SYBCHAR = 47, /* 0x2f */ SYBDATETIME = 61, /* 0x3d */ SYBDATETIME4 = 58, /* 0x3a */ SYBDATETIMN = 111, /* 0x6f */ SYBDECIMAL = 106, /* 0x6a */ SYBFLT8 = 62, /* 0x3e */ SYBFLTN = 109, /* 0x6d */ SYBIMAGE = 34, /* 0x22 */ SYBINT1 = 48, /* 0x30 */ SYBINT2 = 52, /* 0x34 */ SYBINT4 = 56, /* 0x38 */ SYBINT8 = 127, /* 0x7f */ SYBINTN = 38, /* 0x26 */ SYBLONGBINARY = 225, /* 0xe1 */ SYBMONEY = 60, /* 0x3c */ SYBMONEY4 = 122, /* 0x7a */ SYBMONEYN = 110, /* 0x6e */ SYBNTEXT = 99, /* 0x63 */ SYBNUMERIC = 108, /* 0x6c */ SYBNVARCHAR = 103, /* 0x67 */ SYBREAL = 59, /* 0x3b */ SYBSINT1 = 64, /* 0x40 */ SYBTEXT = 35, /* 0x23 */ SYBUINT2 = 65, /* 0x41 */ SYBUINT4 = 66, /* 0x42 */ SYBUINT8 = 67, /* 0x43 */ SYBUNIQUE = 36, /* 0x24 */ SYBVARBINARY = 37, /* 0x25 */ SYBVARCHAR = 39, /* 0x27 */ SYBVARIANT = 98, /* 0x62 */ SYBVOID = 31, /* 0x1f */ XSYBBINARY = 173, /* 0xad */ XSYBCHAR = 175, /* 0xaf */ XSYBNCHAR = 239, /* 0xef */ XSYBNVARCHAR = 231, /* 0xe7 */ XSYBVARBINARY = 165, /* 0xa5 */ XSYBVARCHAR = 167, /* 0xa7 */ TDS_UT_TIMESTAMP = 80, /* User type. */ }; string server_data; string last_error;
1084be2006-02-14Henrik Grubbström (Grubba)  //! Format and report an error.
a451ba2006-02-09Henrik Grubbström (Grubba)  void tds_error(string msg, mixed ... args) { if (sizeof(args)) msg = sprintf(msg, @args);
d8afe72006-02-15Henrik Grubbström (Grubba)  TDS_WERROR("ERROR: %s", msg);
9e76f02006-02-10Henrik Grubbström (Grubba)  predef::error(last_error = msg);
a451ba2006-02-09Henrik Grubbström (Grubba)  }
0b8d2f2013-06-17Martin Nilsson  protected object utf16enc = Charset.encoder("UTF16LE");
9eaf1d2008-06-28Martin Nilsson  protected string string_to_utf16(string s)
a451ba2006-02-09Henrik Grubbström (Grubba)  { return utf16enc->feed(s)->drain(); }
0b8d2f2013-06-17Martin Nilsson  protected object utf16dec = Charset.decoder("UTF16LE");
9eaf1d2008-06-28Martin Nilsson  protected string utf16_to_string(string s)
a451ba2006-02-09Henrik Grubbström (Grubba)  { return utf16dec->feed(s)->drain(); }
1084be2006-02-14Henrik Grubbström (Grubba)  //! A connection to a TDS server.
a451ba2006-02-09Henrik Grubbström (Grubba)  class Connection { int major_version = DEF_MAJOR; int minor_version = DEF_MINOR; int port = DEF_PORT; int block_size = 4096; string server = ""; string server_charset = ""; string hostname = gethostname(); string appname = ""; string username = ""; string password = ""; string language = "";//"us_english"; string library_name = "TDS-Library"; string database = ""; string domain;
1084be2006-02-14Henrik Grubbström (Grubba)  //! The actual TCP connection.
a451ba2006-02-09Henrik Grubbström (Grubba)  Stdio.File socket; #define FMT_SMALLINT "%-2c" #define FMT_INT "%-4c" #define FMT_INT8 "%-4c"
1084be2006-02-14Henrik Grubbström (Grubba)  //! An incoming packet from the TDS server.
ce71272006-02-10Henrik Grubbström (Grubba)  class InPacket
a451ba2006-02-09Henrik Grubbström (Grubba)  {
ce71272006-02-10Henrik Grubbström (Grubba)  int inpos = 0; string inbuf = ""; int done;
9e76f02006-02-10Henrik Grubbström (Grubba) 
9eaf1d2008-06-28Martin Nilsson  protected void fill_buf()
ce71272006-02-10Henrik Grubbström (Grubba)  { if (done) { TDS_WERROR("Filling buffer on finished packet!\n" "%s\n", hex_dump(inbuf));
db22f42006-02-10Henrik Grubbström (Grubba)  tds_error("Filling buffer on finished packet!\n");
ce71272006-02-10Henrik Grubbström (Grubba)  }
9e76f02006-02-10Henrik Grubbström (Grubba) 
ce71272006-02-10Henrik Grubbström (Grubba)  string header = socket->read(8); if (!header || sizeof(header) < 8) { busy = !(done = 1);
05aab12006-02-15Henrik Grubbström (Grubba)  int errno = socket->errno(); Disconnect();
d8afe72006-02-15Henrik Grubbström (Grubba)  predef::error("Failed to read packet header: %O, %s.\n", header, strerror(errno));
ce71272006-02-10Henrik Grubbström (Grubba)  } TDS_WERROR("Read header:\n%s\n", hex_dump(header)); int packet_type; int last_packet; int len; // NOTE: Network byteorder!! sscanf(header, "%-1c%-1c%2c", packet_type, last_packet, len); len -= 8; busy = !(done = last_packet); string data = socket->read(len); if (!data || sizeof(data) < len) { tds_error("Failed to read packet data (%d bytes), got %O (%d bytes), %s\n", len, data, sizeof(data||""), strerror(socket->errno())); } TDS_WERROR("Read packet with %d bytes.\n%s\n", sizeof(data), hex_dump(data)); inbuf = inbuf[inpos..] + data; inpos = 0; }
9e76f02006-02-10Henrik Grubbström (Grubba) 
9eaf1d2008-06-28Martin Nilsson  protected void destroy()
db22f42006-02-10Henrik Grubbström (Grubba)  { // Return the connection to the idle state. while (!done) { inbuf = ""; inpos = 0; fill_buf(); } }
ce71272006-02-10Henrik Grubbström (Grubba)  string get_raw(int bytes) { while (inpos + bytes > sizeof(inbuf)) { fill_buf(); } string raw = inbuf[inpos..inpos + bytes - 1]; inpos += bytes; return raw;
a451ba2006-02-09Henrik Grubbström (Grubba)  }
ce71272006-02-10Henrik Grubbström (Grubba)  string peek_raw(int bytes) { while (inpos + bytes > sizeof(inbuf)) { fill_buf(); } string raw = inbuf[inpos..inpos + bytes - 1]; return raw;
a451ba2006-02-09Henrik Grubbström (Grubba)  }
ce71272006-02-10Henrik Grubbström (Grubba)  int get_int8() { return array_sscanf(get_raw(8), "%-8c")[0]; } int get_int() { return array_sscanf(get_raw(4), "%-4c")[0]; } int get_int_be() { return array_sscanf(get_raw(4), "%4c")[0]; } int get_smallint() { return array_sscanf(get_raw(2), "%-2c")[0]; } int get_byte() { return array_sscanf(get_raw(1), "%-1c")[0]; } int peek_byte() { return array_sscanf(peek_raw(1), "%-1c")[0];
a451ba2006-02-09Henrik Grubbström (Grubba)  }
ce71272006-02-10Henrik Grubbström (Grubba)  string get_string(int len) { if (!len) return ""; TDS_WERROR("get_string(%d)...\n", len); return utf16_to_string(get_raw(len*2)); }
a451ba2006-02-09Henrik Grubbström (Grubba) 
ce71272006-02-10Henrik Grubbström (Grubba)  void expect(string s) { string r = get_raw(sizeof(s)); if (r != s) { tds_error("Expectation failed: Got %O, expected %O\n", r, s); } }
a451ba2006-02-09Henrik Grubbström (Grubba) 
9eaf1d2008-06-28Martin Nilsson  protected void create()
ce71272006-02-10Henrik Grubbström (Grubba)  {
db22f42006-02-10Henrik Grubbström (Grubba)  if (busy) { tds_error("Creating InPacket on busy connection!\n");
ce71272006-02-10Henrik Grubbström (Grubba)  }
db22f42006-02-10Henrik Grubbström (Grubba)  busy = 1;
a451ba2006-02-09Henrik Grubbström (Grubba)  } }
ff17962014-08-15Martin Nilsson  //protected InPacket login_answer;
ce71272006-02-10Henrik Grubbström (Grubba) 
1084be2006-02-14Henrik Grubbström (Grubba)  //! An outgoing packet to the TDS server.
a451ba2006-02-09Henrik Grubbström (Grubba)  class Packet { array(string|int) segments = ({}); array(string) strings = ({}); int flags;
9eaf1d2008-06-28Martin Nilsson  protected void create(int|void flags)
a451ba2006-02-09Henrik Grubbström (Grubba)  {
8e06a32014-09-30Martin Nilsson  this::flags = flags;
a451ba2006-02-09Henrik Grubbström (Grubba)  } void put_int8(int i) { segments += ({ sprintf("%-8c", i) }); } void put_int(int i) { segments += ({ sprintf("%-4c", i) }); } void put_smallint(int i) { segments += ({ sprintf("%-2c", i) }); } void put_byte(int i) { segments += ({ sprintf("%-1c", i) }); } void put_raw(string raw, int len) { if (sizeof(raw) != len) { tds_error("Internal error: unexpected raw length: %O (expected %d)\n", raw, len); } segments += ({raw}); } void put_raw_string(string s) { if (1 || sizeof(s)) { segments += ({ sizeof(strings) }); strings += ({ s }); } else { segments += ({ sprintf("%-2c%-2c", 0, 0) }); } } void put_string(string s) { put_raw_string(string_to_utf16(s)); } void put_long_raw_string(string s) { if (sizeof(s)) { segments += ({ ~sizeof(strings) }); strings += ({ s }); } else { segments += ({ sprintf("%-2c%-2c%-4c", 0, 0, 0) }); } } void put_long_string(string s) { put_long_raw_string(string_to_utf16(s)); } void put_domain_login(string domain, string hostname) { string raw = sprintf("NTLMSSP\0s%-4c%-4c%-2c%-2c%-4c%-2c%-2c%-4c%s%s", 1, 0xb201, sizeof(domain), sizeof(domain), 32 + sizeof(hostname), sizeof(hostname), sizeof(hostname), 32, hostname, domain); segments += ({ sizeof(strings) }); strings += ({ raw }); }
ec1a0f2014-08-16Martin Nilsson  protected string cast(string type)
a451ba2006-02-09Henrik Grubbström (Grubba)  {
ec1a0f2014-08-16Martin Nilsson  if( type!="string" ) return UNDEFINED;
a451ba2006-02-09Henrik Grubbström (Grubba)  int trailer_start = flags && 4; foreach(segments, string|int seg) { trailer_start += stringp(seg)?sizeof(seg):(seg<0)?8:4; } foreach(segments; int i; string|int seg) { if (intp(seg)) { if (seg < 0) { seg = ~seg; segments[i] = sprintf("%-2c%-2c%-4c", sizeof(strings[seg]), sizeof(strings[seg]), trailer_start); TDS_WERROR("Long string %O at offset %d\n", strings[seg], trailer_start); } else { segments[i] = sprintf("%-2c%-2c", trailer_start, sizeof(strings[seg])/2); TDS_WERROR("Short string %O at offset %d\n", strings[seg], trailer_start); } segments += ({ strings[seg] }); trailer_start += sizeof(strings[seg]); } } string res = segments * ""; TDS_WERROR("Generated packet: %O (%d bytes)\n", res, sizeof(res)); if (!flags) return res; return sprintf("%-4c%s", sizeof(res)+4, res); } }
1084be2006-02-14Henrik Grubbström (Grubba)  //! Send a packet to the TDS server. //! //! @note //! May only be called when the connection is idle. //! //! @returns //! If @[last] is true an @[InPacket] with the result //! will be returned.
db22f42006-02-10Henrik Grubbström (Grubba)  InPacket send_packet(Packet p, int flag, int|void last)
a451ba2006-02-09Henrik Grubbström (Grubba)  {
9e76f02006-02-10Henrik Grubbström (Grubba)  if (busy) {
db22f42006-02-10Henrik Grubbström (Grubba)  tds_error("Sending packet on busy connection!\n");
9e76f02006-02-10Henrik Grubbström (Grubba)  }
db22f42006-02-10Henrik Grubbström (Grubba) 
d8afe72006-02-15Henrik Grubbström (Grubba)  array(string) packets = ((string) p)/(float)(block_size-8);
9855ff2006-02-10Henrik Grubbström (Grubba)  foreach(packets; int i; string raw) {
db22f42006-02-10Henrik Grubbström (Grubba)  // NOTE: Network byteorder!! raw = sprintf("%1c%1c%2c\0\0%1c\0%s",
9855ff2006-02-10Henrik Grubbström (Grubba)  flag, (i == (sizeof(packets)-1)) && last,
db22f42006-02-10Henrik Grubbström (Grubba)  sizeof(raw) + 8, 1, /* TDS 7 or 8. */ raw); TDS_WERROR("Wrapped packet: %O\n%s\n", raw, hex_dump(raw)); if (socket->write(raw) != sizeof(raw)) {
05aab12006-02-15Henrik Grubbström (Grubba)  Disconnect();
d8afe72006-02-15Henrik Grubbström (Grubba)  predef::error("Failed to send packet.\n" "raw: %O\n", raw);
db22f42006-02-10Henrik Grubbström (Grubba)  }
a451ba2006-02-09Henrik Grubbström (Grubba)  }
ce71272006-02-10Henrik Grubbström (Grubba)  //Stdio.write_file("packet.bin", raw);
db22f42006-02-10Henrik Grubbström (Grubba)  if (last) return InPacket(); return 0;
a451ba2006-02-09Henrik Grubbström (Grubba)  }
9eaf1d2008-06-28Martin Nilsson  protected string crypt_pass(string password)
a451ba2006-02-09Henrik Grubbström (Grubba)  { password = string_to_utf16(password); password ^= "\x5a"*sizeof(password); return (string)map((array)password, lambda(int byte) { return ((byte >> 4) | (byte << 4)) & 0xff; }); }
9eaf1d2008-06-28Martin Nilsson  protected InPacket send_login()
a451ba2006-02-09Henrik Grubbström (Grubba)  { password = password[..127]; Packet p = Packet(1); p->put_raw(TDS8VERSION, 4); // TDS version p->put_int(0); // block size p->put_raw(CLIENTVERSION, 4); // client version p->put_int(getpid()); // pid p->put_raw(CONNECTIONID, 4); // connection identifier p->put_byte(0x80|0x40|0x20); // warnings + force select db p->put_byte(3|(domain && 0x80)); // domain login p->put_byte(0); // sql type p->put_byte(0); // reserved p->put_raw(TIMEZONE, 4); // time zone p->put_raw(COLLATION, 4); // collation p->put_string(hostname); // hostname if (domain) { p->put_string(""); p->put_string(""); } else { p->put_string(username); // user name p->put_raw_string(crypt_pass(password)); // password } p->put_string(appname); p->put_string(server); p->put_smallint(0); // Unknown p->put_smallint(0); // Unknown p->put_string(""); p->put_string(language); p->put_string(database); p->put_raw("\0"*6, 6); // MAC address if (domain) { p->put_domain_login(domain, hostname); } else { p->put_string(""); } p->put_string(""); TDS_WERROR("Sending login packet.\n");
db22f42006-02-10Henrik Grubbström (Grubba)  return send_packet(p, 0x10, 1);
a451ba2006-02-09Henrik Grubbström (Grubba)  } string ecb_encrypt(string data, string key) {
26d74c2006-02-10Henrik Grubbström (Grubba) #if constant(Crypto.DES)
a451ba2006-02-09Henrik Grubbström (Grubba)  Crypto.DES des = Crypto.DES(); des->set_encrypt_key(des->fix_parity(key));
26d74c2006-02-10Henrik Grubbström (Grubba) #else Crypto.des des = Crypto.des(); des->set_encrypt_key(Crypto.des_parity(key)); #endif
a451ba2006-02-09Henrik Grubbström (Grubba)  return des->crypt(data); } string encrypt_answer(string hash, string nonce) { return ecb_encrypt(nonce, hash[..6]) + ecb_encrypt(nonce, hash[7..13]) + ecb_encrypt(nonce, hash[14..]); } string answer_lan_mgr_challenge(string passwd, string nonce) { string magic = "KGS!@#$%"; /* FIXME: Filter wide characters in passwd! */ passwd = upper_case((passwd + "\0"*14)[..13]); string hash = ecb_encrypt(magic, passwd[..6]) + ecb_encrypt(magic, passwd[7..]); return encrypt_answer(hash, nonce); } string answer_nt_challenge(string passwd, string nonce) { string nt_passwd = string_to_utf16(passwd);
26d74c2006-02-10Henrik Grubbström (Grubba) #if constant(Crypto.MD4)
a451ba2006-02-09Henrik Grubbström (Grubba)  Crypto.MD4 md4 = Crypto.MD4();
26d74c2006-02-10Henrik Grubbström (Grubba) #else
06134b2006-03-17Henrik Grubbström (Grubba) #if constant(Crypto.md4)
26d74c2006-02-10Henrik Grubbström (Grubba)  Crypto.md4 md4 = Crypto.md4();
06134b2006-03-17Henrik Grubbström (Grubba) #else
f628272006-04-05Henrik Grubbström (Grubba)  predef::error("MD4 hashes not supported in this Pike.\n");
06134b2006-03-17Henrik Grubbström (Grubba)  mixed md4; #endif
26d74c2006-02-10Henrik Grubbström (Grubba) #endif
a451ba2006-02-09Henrik Grubbström (Grubba)  md4->update(nt_passwd); return encrypt_answer(md4->digest() + "\0"*16, nonce); }
9eaf1d2008-06-28Martin Nilsson  protected void send_auth(string nonce)
a451ba2006-02-09Henrik Grubbström (Grubba)  { int out_flag = 0x11; Packet p = Packet(); p->put_raw("NTLMSSP\0", 8); p->put_int(3); /* sequence 3 */ p->put_long_raw_string(answer_lan_mgr_challenge(password, nonce)); p->put_long_raw_string(answer_nt_challenge(password, nonce)); p->put_long_string(domain); p->put_long_string(username); p->put_long_string(hostname); p->put_long_string(""); /* Unknown */ p->put_int(0x8201); /* flags */ TDS_WERROR("Sending auth packet.\n"); send_packet(p, 0x11); }
9eaf1d2008-06-28Martin Nilsson  protected void process_auth(InPacket inp)
a451ba2006-02-09Henrik Grubbström (Grubba)  {
ce71272006-02-10Henrik Grubbström (Grubba)  int pdu_size = inp->get_smallint();
a451ba2006-02-09Henrik Grubbström (Grubba)  if (pdu_size < 32) tds_error("Bad pdu size: %d\n", pdu_size);
ce71272006-02-10Henrik Grubbström (Grubba)  inp->expect("NTLMSSP\0"); inp->get_int(); /* sequence -> 2 */ inp->get_int(); /* domain len * 2 */ inp->get_int(); /* domain offset */ inp->get_int(); /* flags */ string nonce = inp->get_raw(8);
a451ba2006-02-09Henrik Grubbström (Grubba)  /* Discard context, target and data info */
ce71272006-02-10Henrik Grubbström (Grubba)  inp->get_raw(pdu_size - 32);
a451ba2006-02-09Henrik Grubbström (Grubba)  TDS_WERROR("Got nonce: %O\n", nonce); send_auth(nonce); }
9eaf1d2008-06-28Martin Nilsson  protected void process_msg(InPacket inp, int token_type)
a451ba2006-02-09Henrik Grubbström (Grubba)  { TDS_WERROR("TDS_ERROR_TOKEN | TDS_INFO_TOKEN | TDS_EED_TOKEN\n");
ce71272006-02-10Henrik Grubbström (Grubba)  int len = inp->get_smallint(); int no = inp->get_int(); int state = inp->get_byte(); int level = inp->get_byte();
a451ba2006-02-09Henrik Grubbström (Grubba)  int is_error = 0; int has_eed = 0; switch(token_type) { case TDS_EED_TOKEN: TDS_WERROR("TDS_EED_TOKEN\n"); if (level > 10) is_error = 1;
ce71272006-02-10Henrik Grubbström (Grubba)  int state_len = inp->get_byte(); string state = inp->get_raw(state_len); has_eed = inp->get_byte();
a451ba2006-02-09Henrik Grubbström (Grubba)  /* Ignore status and transaction state */
ce71272006-02-10Henrik Grubbström (Grubba)  inp->get_smallint();
a451ba2006-02-09Henrik Grubbström (Grubba)  break; case TDS_INFO_TOKEN: TDS_WERROR("TDS_INFO_TOKEN\n"); break; case TDS_ERROR_TOKEN: TDS_WERROR("TDS_ERROR_TOKEN\n"); is_error = 1; break; }
ce71272006-02-10Henrik Grubbström (Grubba)  string message = inp->get_string(inp->get_smallint()); string server = inp->get_string(inp->get_byte()); string proc_name = inp->get_string(inp->get_byte()); int line = inp->get_smallint();
a451ba2006-02-09Henrik Grubbström (Grubba)  if (has_eed) { while (1) {
ce71272006-02-10Henrik Grubbström (Grubba)  switch(inp->peek_byte()) {
a451ba2006-02-09Henrik Grubbström (Grubba)  case TDS5_PARAMFMT_TOKEN: case TDS5_PARAMFMT2_TOKEN: case TDS5_PARAMS_TOKEN:
ce71272006-02-10Henrik Grubbström (Grubba)  process_default_tokens(inp, inp->get_byte());
a451ba2006-02-09Henrik Grubbström (Grubba)  continue; } break; } } if (is_error) { tds_error("%d: %s:%s:%d %s\n", level, proc_name, server, line, message); } else {
d8afe72006-02-15Henrik Grubbström (Grubba) #if 0
c5c1542006-02-14Henrik Grubbström (Grubba)  last_error = sprintf("%d: %s:%s:%d %s\n", level, proc_name, server, line, message);
d8afe72006-02-15Henrik Grubbström (Grubba) #endif
a451ba2006-02-09Henrik Grubbström (Grubba)  TDS_WERROR("%d: %s:%s:%d %s\n", level, proc_name, server, line, message); } }
9eaf1d2008-06-28Martin Nilsson  protected void process_env_chg(InPacket inp)
a451ba2006-02-09Henrik Grubbström (Grubba)  {
ce71272006-02-10Henrik Grubbström (Grubba)  int size = inp->get_smallint(); int env_type = inp->get_byte();
a451ba2006-02-09Henrik Grubbström (Grubba)  if (env_type == TDS_ENV_SQLCOLLATION) {
ce71272006-02-10Henrik Grubbström (Grubba)  size = inp->get_byte(); string block = inp->get_raw(size);
a451ba2006-02-09Henrik Grubbström (Grubba)  if (size >= 5) { string collation = block[..4]; int lcid; sscanf(collation, "%-3c", lcid); /* FIXME: Should we care? */ } /* Discard old collation. */
ce71272006-02-10Henrik Grubbström (Grubba)  inp->get_raw(inp->get_byte());
a451ba2006-02-09Henrik Grubbström (Grubba)  return; }
ce71272006-02-10Henrik Grubbström (Grubba)  string new_val = inp->get_string(inp->get_byte()); string old_val = inp->get_string(inp->get_byte());
a451ba2006-02-09Henrik Grubbström (Grubba)  switch(env_type) { case TDS_ENV_PACKSIZE: int new_block_size = (int)new_val; TDS_WERROR("Change block size from %O to %O\n", old_val, new_val); break; case TDS_ENV_DATABASE: TDS_WERROR("Database changed to %O\n", new_val); break; case TDS_ENV_LANG: TDS_WERROR("Language changed to %O from %O\n", new_val, old_val); break; case TDS_ENV_CHARSET: TDS_WERROR("Charset changed from %O to %O\n", new_val, old_val); break; } } int get_size_by_type(int col_type) { switch (col_type) { case SYBINT1: case SYBBIT: case SYBBITN: return 1; case SYBINT2: return 2; case SYBINT4: case SYBREAL: case SYBDATETIME4: case SYBMONEY4: return 4; case SYBINT8: case SYBFLT8: case SYBDATETIME: case SYBMONEY: return 8; case SYBUNIQUE: return 16; default: return -1; } } int get_cardinal_type(int col_type) {
c5c1542006-02-14Henrik Grubbström (Grubba)  // FIXME: This function could be done away with.
a451ba2006-02-09Henrik Grubbström (Grubba)  switch (col_type) { case XSYBVARBINARY: return SYBVARBINARY; case XSYBBINARY: return SYBBINARY; case SYBNTEXT: return SYBTEXT; case XSYBNVARCHAR: case XSYBVARCHAR: return SYBVARCHAR; case XSYBNCHAR: case XSYBCHAR: return SYBCHAR; } return col_type; } int get_varint_size(int col_type) { switch (col_type) { case SYBLONGBINARY: case SYBTEXT: case SYBNTEXT: case SYBIMAGE: case SYBVARIANT: return 4; case SYBVOID: case SYBINT1: case SYBBIT: case SYBINT2: case SYBINT4: case SYBINT8: case SYBDATETIME4: case SYBREAL: case SYBMONEY: case SYBDATETIME: case SYBFLT8: case SYBMONEY4: case SYBSINT1: case SYBUINT2: case SYBUINT4: case SYBUINT8: return 0; case XSYBNCHAR: case XSYBNVARCHAR: case XSYBCHAR: case XSYBVARCHAR: case XSYBBINARY: case XSYBVARBINARY: return 2; default: return 1; } } int is_unicode_type(int col_type) { return (col_type == XSYBNVARCHAR) || (col_type == XSYBNCHAR) || (col_type == SYBNTEXT); } int is_ascii_type(int col_type) { return (<XSYBCHAR,XSYBVARCHAR,SYBTEXT,SYBCHAR,SYBVARCHAR>)[col_type]; } int is_char_type(int col_type) { return is_unicode_type(col_type) || is_ascii_type(col_type); } int is_blob_type(int col_type) { return (col_type == SYBTEXT) || (col_type == SYBIMAGE) || (col_type == SYBNTEXT); } int is_collate_type(int col_type) { return (col_type==XSYBVARCHAR) || (col_type==XSYBCHAR) || (col_type==SYBTEXT) || (col_type==XSYBNVARCHAR) || (col_type==XSYBNCHAR) || (col_type==SYBNTEXT); } int is_numeric_type(int col_type) { return (col_type == SYBNUMERIC) || (col_type == SYBDECIMAL); }
ce71272006-02-10Henrik Grubbström (Grubba)  mapping(string:mixed) tds7_get_data_info(InPacket inp)
a451ba2006-02-09Henrik Grubbström (Grubba)  { mapping(string:mixed) res = ([
ce71272006-02-10Henrik Grubbström (Grubba)  "usertype":inp->get_smallint(), "flags":inp->get_smallint(), "column_type":inp->get_byte(),
a451ba2006-02-09Henrik Grubbström (Grubba)  ]); res->nullable == res->flags & 0x01; res->writeable == !!(res->flags & 0x08); res->identity == !!(res->flags & 0x10); res->cardinal_type = get_cardinal_type(res->column_type); res->varint_size = get_varint_size(res->column_type); switch(res->varint_size) { case 0: res->column_size = get_size_by_type(res->column_type); break; case 4:
ce71272006-02-10Henrik Grubbström (Grubba)  res->column_size = inp->get_int();
a451ba2006-02-09Henrik Grubbström (Grubba)  break; case 2:
ce71272006-02-10Henrik Grubbström (Grubba)  res->column_size = inp->get_smallint();
a451ba2006-02-09Henrik Grubbström (Grubba)  break; case 1:
ce71272006-02-10Henrik Grubbström (Grubba)  res->column_size = inp->get_byte();
a451ba2006-02-09Henrik Grubbström (Grubba)  break; } res->timestamp = (res->cardinal_type == SYBBINARY) || (res->cardinal_type == TDS_UT_TIMESTAMP); if (is_numeric_type(res->cardinal_type)) { TDS_WERROR("is_numeric\n");
ce71272006-02-10Henrik Grubbström (Grubba)  res->column_prec = inp->get_byte(); res->column_scale = inp->get_byte();
a451ba2006-02-09Henrik Grubbström (Grubba)  } if (is_collate_type(res->column_type)) { TDS_WERROR("is_collate\n");
ce71272006-02-10Henrik Grubbström (Grubba)  inp->get_raw(4); // Collation inp->get_byte(); // charset
a451ba2006-02-09Henrik Grubbström (Grubba)  } if (is_blob_type(res->cardinal_type)) { TDS_WERROR("is_blob\n");
ce71272006-02-10Henrik Grubbström (Grubba)  res->table = inp->get_string(inp->get_smallint());
a451ba2006-02-09Henrik Grubbström (Grubba)  }
ce71272006-02-10Henrik Grubbström (Grubba)  res->name = inp->get_string(inp->get_byte());
a451ba2006-02-09Henrik Grubbström (Grubba)  TDS_WERROR("Column info: %O\n", res); return res; }
9eaf1d2008-06-28Martin Nilsson  protected array(mapping(string:mixed)) tds7_process_result(InPacket inp)
a451ba2006-02-09Henrik Grubbström (Grubba)  {
ce71272006-02-10Henrik Grubbström (Grubba)  int num_cols = inp->get_smallint();
a451ba2006-02-09Henrik Grubbström (Grubba)  if (num_cols == 0xffff) { TDS_WERROR("No meta data.\n");
db22f42006-02-10Henrik Grubbström (Grubba)  return 0;
a451ba2006-02-09Henrik Grubbström (Grubba)  } TDS_WERROR("%d columns in result.\n", num_cols);
db22f42006-02-10Henrik Grubbström (Grubba)  array(mapping(string:mixed)) column_info = allocate(num_cols);
a451ba2006-02-09Henrik Grubbström (Grubba)  foreach(column_info; int col; ) {
ce71272006-02-10Henrik Grubbström (Grubba)  column_info[col] = tds7_get_data_info(inp);
a451ba2006-02-09Henrik Grubbström (Grubba)  }
db22f42006-02-10Henrik Grubbström (Grubba)  return column_info;
a451ba2006-02-09Henrik Grubbström (Grubba)  }
9eaf1d2008-06-28Martin Nilsson  protected void process_default_tokens(InPacket inp, int token_type)
a451ba2006-02-09Henrik Grubbström (Grubba)  { if (token_type == TDS_DONE_TOKEN) return; switch(token_type) { case TDS_DONE_TOKEN: return; case TDS_ERROR_TOKEN: case TDS_INFO_TOKEN: case TDS_EED_TOKEN:
ce71272006-02-10Henrik Grubbström (Grubba)  process_msg(inp, token_type);
a451ba2006-02-09Henrik Grubbström (Grubba)  return; case TDS_ENVCHANGE_TOKEN:
ce71272006-02-10Henrik Grubbström (Grubba)  process_env_chg(inp);
a451ba2006-02-09Henrik Grubbström (Grubba)  return; case TDS7_RESULT_TOKEN:
db22f42006-02-10Henrik Grubbström (Grubba)  tds_error("TDS7_RESULT_TOKEN in default handler!\n"); //tds7_process_result(inp);
a451ba2006-02-09Henrik Grubbström (Grubba)  break;
faa3c72006-02-10Henrik Grubbström (Grubba)  case TDS5_DYNAMIC_TOKEN: case TDS_LOGINACK_TOKEN: case TDS_ORDERBY_TOKEN: case TDS_CONTROL_TOKEN: case TDS_TABNAME_TOKEN: TDS_WERROR("Skipping token: %d\n", token_type);
ce71272006-02-10Henrik Grubbström (Grubba)  inp->get_raw(inp->get_smallint());
faa3c72006-02-10Henrik Grubbström (Grubba)  break; default:
c5c1542006-02-14Henrik Grubbström (Grubba)  werror("TDS: WARNING: Got unknown token in process_default_tokens: %d\n", token_type);
faa3c72006-02-10Henrik Grubbström (Grubba)  break;
a451ba2006-02-09Henrik Grubbström (Grubba)  } }
db22f42006-02-10Henrik Grubbström (Grubba)  array(mapping(string:mixed)) process_result_tokens(InPacket inp)
a451ba2006-02-09Henrik Grubbström (Grubba)  {
db22f42006-02-10Henrik Grubbström (Grubba)  array(mapping(string:mixed)) column_info;
a451ba2006-02-09Henrik Grubbström (Grubba)  while (1) {
ce71272006-02-10Henrik Grubbström (Grubba)  int token_type = inp->peek_byte();
a451ba2006-02-09Henrik Grubbström (Grubba)  TDS_WERROR("Got result token %d\n", token_type); switch(token_type) { case TDS7_RESULT_TOKEN: TDS_WERROR("TDS7_RESULT_TOKEN\n");
ce71272006-02-10Henrik Grubbström (Grubba)  inp->get_byte();
db22f42006-02-10Henrik Grubbström (Grubba)  column_info = tds7_process_result(inp);
3f58f82006-02-16Henrik Grubbström (Grubba) #if 0
ce71272006-02-10Henrik Grubbström (Grubba)  if (inp->peek_byte() == TDS_TABNAME_TOKEN) {
a451ba2006-02-09Henrik Grubbström (Grubba)  TDS_WERROR("TDS_TABNAME_TOKEN\n");
ce71272006-02-10Henrik Grubbström (Grubba)  process_default_tokens(inp, inp->get_byte()); if (inp->peek_byte() == TDS_COLINFO_TOKEN) {
a451ba2006-02-09Henrik Grubbström (Grubba)  TDS_WERROR("TDS_COLINFO_TOKEN\n");
ce71272006-02-10Henrik Grubbström (Grubba)  inp->get_byte();
a451ba2006-02-09Henrik Grubbström (Grubba)  //process_colinfo(); // FIXME! } }
3f58f82006-02-16Henrik Grubbström (Grubba) #endif /* 0 */ break; case TDS_TABNAME_TOKEN: inp->get_byte(); string raw = inp->get_raw(inp->get_smallint());
48b40f2007-04-13Henrik Grubbström (Grubba)  werror("Got TDS_TABNAME_TOKEN: %O\n"
3f58f82006-02-16Henrik Grubbström (Grubba)  "column_info: %O\n", raw, column_info); break; case TDS_COLINFO_TOKEN: inp->get_byte(); string colinfo = inp->get_raw(inp->get_smallint()); if (column_info) { while (sizeof(colinfo) > 2) { int colno = colinfo[0]; int flags = colinfo[2]; colinfo = colinfo[3..]; if (!colno || (colno > sizeof(column_info))) { break; } column_info[colno]->writeable = !(flags & 0x4); column_info[colno]->identity = !!(flags & 0x8); column_info[colno]->hidden = !!(flags & 0x10); if ((flags & 0x20) && sizeof(colinfo)) { int len = colinfo[0]<<1; string raw = colinfo[1..len]; colinfo = colinfo[len+1..]; column_info[colno]->real_name = utf16_to_string(raw); } } } else { TDS_WERROR("TDS_COLINFO_TOKEN without active column info\n" " raw: %O\n", colinfo); }
db22f42006-02-10Henrik Grubbström (Grubba)  break;
48b40f2007-04-13Henrik Grubbström (Grubba)  case TDS_DONEINPROC_TOKEN: inp->get_byte(); int flags = inp->get_smallint(); int state = inp->get_smallint(); TDS_WERROR("TDS_DONEINPROC_TOKEN: flags: 0x%04x, state: 0x%04x\n", flags, state); if (flags & 0x10) { // Count field is valid. int rows_affected = inp->get_int(); TDS_WERROR(" rows_affected: %d\n", rows_affected); } else { inp->get_int(); // Invalid. } if (flags & 1) { // More results pending. continue; } return column_info; case TDS_DONEPROC_TOKEN: TDS_WERROR(" TDS_DONEINPROC_TOKEN pending\n"); return column_info;
9e76f02006-02-10Henrik Grubbström (Grubba)  case TDS_DONE_TOKEN:
db22f42006-02-10Henrik Grubbström (Grubba)  TDS_WERROR(" TDS_DONE_TOKEN pending\n"); return column_info;
a451ba2006-02-09Henrik Grubbström (Grubba)  case TDS_ROW_TOKEN:
db22f42006-02-10Henrik Grubbström (Grubba)  TDS_WERROR(" TDS_ROW_TOKEN pending\n"); return column_info;
0db6b12006-02-16Henrik Grubbström (Grubba)  case TDS_RETURNSTATUS_TOKEN: inp->get_byte(); int ret_status = inp->get_int(); TDS_WERROR("Return status: %d\n", ret_status); break;
a451ba2006-02-09Henrik Grubbström (Grubba)  default:
c5c1542006-02-14Henrik Grubbström (Grubba)  werror("TDS: WARNING: Unhandled token in process_result_tokens: %d\n", token_type);
ce71272006-02-10Henrik Grubbström (Grubba)  // FALL_THROUGH case TDS_ORDERBY_TOKEN:
db22f42006-02-10Henrik Grubbström (Grubba)  /* Ignore. */
e8a2132006-02-16Henrik Grubbström (Grubba)  case TDS_ERROR_TOKEN: case TDS_INFO_TOKEN: case TDS_EED_TOKEN:
ce71272006-02-10Henrik Grubbström (Grubba)  inp->get_byte(); process_default_tokens(inp, token_type);
a451ba2006-02-09Henrik Grubbström (Grubba)  break; } } }
9eaf1d2008-06-28Martin Nilsson  protected string|int get_data(InPacket inp,
ce71272006-02-10Henrik Grubbström (Grubba)  mapping(string:mixed) info, int col)
a451ba2006-02-09Henrik Grubbström (Grubba)  { TDS_WERROR("get_data for column %d info:%O\n", col, info); mapping blobinfo; int colsize = 0; outer: switch(info->varint_size) { case 4: switch(info->cardinal_type) { case SYBVARIANT:
ce71272006-02-10Henrik Grubbström (Grubba)  int sz = inp->get_int();
a451ba2006-02-09Henrik Grubbström (Grubba)  if (!sz) return 0; // NULL;
ce71272006-02-10Henrik Grubbström (Grubba)  return inp->get_raw(sz);
a451ba2006-02-09Henrik Grubbström (Grubba)  case SYBLONGBINARY:
ce71272006-02-10Henrik Grubbström (Grubba)  colsize = inp->get_int();
a451ba2006-02-09Henrik Grubbström (Grubba)  break outer; }
ce71272006-02-10Henrik Grubbström (Grubba)  int len = inp->get_byte();
a451ba2006-02-09Henrik Grubbström (Grubba)  if (len == 16) { blobinfo = ([
ce71272006-02-10Henrik Grubbström (Grubba)  "textptr":inp->get_raw(16), "timestamp":inp->get_raw(8),
a451ba2006-02-09Henrik Grubbström (Grubba)  ]);
ce71272006-02-10Henrik Grubbström (Grubba)  colsize = inp->get_int();
a451ba2006-02-09Henrik Grubbström (Grubba)  TDS_WERROR("BLOB: size:%d info:%O\n", colsize, blobinfo); } break; case 2:
ce71272006-02-10Henrik Grubbström (Grubba)  colsize = inp->get_smallint();
a451ba2006-02-09Henrik Grubbström (Grubba)  if (!colsize) { // Empty string. return ""; } if (colsize == 0xffff) colsize = 0; break;
faa3c72006-02-10Henrik Grubbström (Grubba)  case 1:
ce71272006-02-10Henrik Grubbström (Grubba)  colsize = inp->get_byte();
faa3c72006-02-10Henrik Grubbström (Grubba)  break;
a451ba2006-02-09Henrik Grubbström (Grubba)  case 0: colsize = get_size_by_type(info->cardinal_type); break; } TDS_WERROR("Column size is %d\n", colsize); if (!colsize) return 0; // NULL. if (is_numeric_type(info->cardinal_type)) { // NUMERIC
ce71272006-02-10Henrik Grubbström (Grubba)  string arr = inp->get_raw(colsize);
a451ba2006-02-09Henrik Grubbström (Grubba)  TDS_WERROR("NUMERIC: %O\n", arr); return arr; // FIXME } else if (is_blob_type(info->cardinal_type)) { // BLOB
ce71272006-02-10Henrik Grubbström (Grubba)  string raw = inp->get_raw(colsize);
a451ba2006-02-09Henrik Grubbström (Grubba)  TDS_WERROR("BLOB. colsize:%d, raw: %O\n", colsize, raw); if (is_char_type(info->cardinal_type)) {
c5c1542006-02-14Henrik Grubbström (Grubba)  // FIXME: Move this to convert()?
a451ba2006-02-09Henrik Grubbström (Grubba)  return utf16_to_string(raw); } return raw; } else {
ce71272006-02-10Henrik Grubbström (Grubba)  string raw = inp->get_raw(colsize);
a451ba2006-02-09Henrik Grubbström (Grubba)  TDS_WERROR("Got raw data: %O\ncolumn_size:%d colsize:%d\n%s\n", raw, info->column_size, colsize, hex_dump(raw));
9e76f02006-02-10Henrik Grubbström (Grubba)  if (is_unicode_type(info->column_type)) {
c5c1542006-02-14Henrik Grubbström (Grubba)  // FIXME: Move this to convert()?
9e76f02006-02-10Henrik Grubbström (Grubba)  raw = utf16_to_string(raw); }
a451ba2006-02-09Henrik Grubbström (Grubba)  if (colsize < info->column_size) { // Handle padding. switch(info->cardinal_type) { case SYBLONGBINARY: // FIXME: ?? break; case SYBCHAR: case XSYBCHAR: raw += " "*(info->column_size - colsize); break; case SYBBINARY: case XSYBBINARY: raw += "\0"*(info->column_size - colsize); break; } } if (info->cardinal_type == SYBDATETIME4) { TDS_WERROR("Datetime4:%{ %d}\n", (array)raw); } TDS_WERROR("ROW: %O\n", raw); return raw; } }
9eaf1d2008-06-28Martin Nilsson  protected string|int convert(string|int raw, mapping(string:mixed) info)
a451ba2006-02-09Henrik Grubbström (Grubba)  {
5495b42006-02-13Henrik Grubbström (Grubba)  if (!raw) { TDS_CONV_WERROR("%O ==> NULL\n", raw); return raw; /* NULL */ }
6363722006-02-10Henrik Grubbström (Grubba)  int cardinal_type; switch(cardinal_type = info->cardinal_type) {
a451ba2006-02-09Henrik Grubbström (Grubba)  case SYBCHAR: case SYBVARCHAR: case SYBTEXT: case XSYBCHAR: case XSYBVARCHAR: case SYBNVARCHAR: case SYBNTEXT: case SYBLONGBINARY: case SYBBINARY: case SYBVARBINARY: case SYBIMAGE: case XSYBBINARY: case XSYBVARBINARY:
c5c1542006-02-14Henrik Grubbström (Grubba)  // Strings have already been converted.
5495b42006-02-13Henrik Grubbström (Grubba)  TDS_CONV_WERROR("%O ==> %O\n", raw, raw);
a451ba2006-02-09Henrik Grubbström (Grubba)  return raw;
5495b42006-02-13Henrik Grubbström (Grubba)  case SYBMONEYN:
a451ba2006-02-09Henrik Grubbström (Grubba)  case SYBMONEY4: case SYBMONEY: { int val; string sgn = "";
5495b42006-02-13Henrik Grubbström (Grubba)  if (sizeof(raw) == 8) { sscanf(raw, "%-8c", val); } else { sscanf(raw, "%-4c", val); }
a451ba2006-02-09Henrik Grubbström (Grubba)  if (val < 0) { sgn = "-"; val = -val; } int cents = (val + 50)/100;
5495b42006-02-13Henrik Grubbström (Grubba)  string res = sprintf("%s%d.%02d", sgn, cents/100, cents%100); TDS_CONV_WERROR("%O ==> %O\n", raw, res); return res;
a451ba2006-02-09Henrik Grubbström (Grubba)  } case SYBNUMERIC:
fcfc4c2015-02-06Henrik Grubbström (Grubba)  case SYBDECIMAL:
5495b42006-02-13Henrik Grubbström (Grubba)  { string res = sprintf("%d", array_sscanf(raw[1..], "%-" + (sizeof(raw)-1) + "c")[0]); // Move decimal point @[scale] positions. int scale = info->column_scale; if (sizeof(res) < scale) { res = "0." + ("0" * (scale - sizeof(res))) + res; } else if (sizeof(res) == scale) { res = "0." + res; } else if (scale) {
48b40f2007-04-13Henrik Grubbström (Grubba)  res = res[..sizeof(res)-2] + "." +
5495b42006-02-13Henrik Grubbström (Grubba)  res[sizeof(res)-scale..]; } // Fix the sign.
4757242015-02-09Henrik Grubbström (Grubba)  if (!raw[0]) {
5495b42006-02-13Henrik Grubbström (Grubba)  res = "-" + res; } TDS_CONV_WERROR("%O (scale: %d) ==> %O\n", raw, scale, res); return res; }
d447602015-02-06Henrik Grubbström (Grubba)  case SYBUNIQUE: { return Standards.UUID.UUID(raw)->str(); }
81cefd2015-02-06Henrik Grubbström (Grubba)  case SYBFLTN: { return sprintf("%g", @array_sscanf(raw, "%-" + sizeof(raw) + "F")); }
a451ba2006-02-09Henrik Grubbström (Grubba)  case SYBREAL: case SYBFLT8: default: // FIXME:
5495b42006-02-13Henrik Grubbström (Grubba)  TDS_CONV_WERROR("Not yet supported: %d (%O)\n", info->cardinal_type, raw);
c5c1542006-02-14Henrik Grubbström (Grubba)  werror("TDS: WARNING: Datatype %d not yet supported. raw: %O\n", info->cardinal_type, raw);
a451ba2006-02-09Henrik Grubbström (Grubba)  return raw; case SYBBIT: case SYBBITN:
5495b42006-02-13Henrik Grubbström (Grubba)  TDS_CONV_WERROR("%O ==> \"%d\"", raw, !!raw[0]);
a451ba2006-02-09Henrik Grubbström (Grubba)  return raw[0]?"1":"0"; case SYBINT1: case SYBINT2: case SYBINT4: case SYBINT8:
9e76f02006-02-10Henrik Grubbström (Grubba)  case SYBINTN: { int val; if (raw[-1]& 0x80) { // Negative. val = ~array_sscanf((~raw) + ("\x00"*8), "%-8c")[0]; } else { val = array_sscanf(raw + ("\x00"*8), "%-8c")[0]; }
c5c1542006-02-14Henrik Grubbström (Grubba)  TDS_CONV_WERROR("%O ==> \"%d\"\n", raw, val);
9e76f02006-02-10Henrik Grubbström (Grubba)  return sprintf("%d", val); }
6363722006-02-10Henrik Grubbström (Grubba)  case SYBDATETIMN:
a451ba2006-02-09Henrik Grubbström (Grubba)  case SYBDATETIME: case SYBDATETIME4: { int day, min, sec, sec_300;
5495b42006-02-13Henrik Grubbström (Grubba)  if (sizeof(raw) == 8) {
a451ba2006-02-09Henrik Grubbström (Grubba)  sscanf(raw, "%-4c%-4c", day, sec_300); sec = sec_300/300; sec_300 %= 300; min = sec/60; sec %= 60; } else { sscanf(raw, "%-2c%-2c", day, min); } int hour = min/60; min %= 60; int l = day + 146038; int century = (l * 4)/146097; l -= (century*146097 + 3)/4; int year = ((l + 1)*4000)/1461001; l -= (year * 1461)/4; int yday = (l > 306)?(l - 305):(l + 60); l += 31; int j = (l * 80)/2447; int mday = l - (j * 2447)/80; l = j/11; int mon = j + 1 - l*12; year += (century + 15)*100 + l; if (!l && !(year & 3) && ((year % 100) || !(year % 400))) yday++;
5495b42006-02-13Henrik Grubbström (Grubba)  string res = sprintf("%04d-%02d-%02dT%02d:%02d:%02d",
cadd822006-02-23Henrik Grubbström (Grubba)  year, mon+1, mday, hour, min, sec);
5495b42006-02-13Henrik Grubbström (Grubba)  TDS_CONV_WERROR("%O ==> %O\n", raw, res); return res;
a451ba2006-02-09Henrik Grubbström (Grubba)  } } }
9eaf1d2008-06-28Martin Nilsson  protected array(string|int) process_row(InPacket inp,
db22f42006-02-10Henrik Grubbström (Grubba)  array(mapping(string:mixed)) col_info)
a451ba2006-02-09Henrik Grubbström (Grubba)  {
db22f42006-02-10Henrik Grubbström (Grubba)  if (!col_info) return 0; array(string|int) res = allocate(sizeof(col_info)); foreach(col_info; int i; mapping(string:mixed) info) {
ce71272006-02-10Henrik Grubbström (Grubba)  res[i] = convert(get_data(inp, info, i), info);
a451ba2006-02-09Henrik Grubbström (Grubba)  } return res; }
ce71272006-02-10Henrik Grubbström (Grubba)  array(string|int) process_row_tokens(InPacket inp,
db22f42006-02-10Henrik Grubbström (Grubba)  array(mapping(string:mixed)) col_info,
ce71272006-02-10Henrik Grubbström (Grubba)  int|void leave_end_token)
a451ba2006-02-09Henrik Grubbström (Grubba)  {
9e76f02006-02-10Henrik Grubbström (Grubba)  //if (!busy) return 0; // No more rows.
a451ba2006-02-09Henrik Grubbström (Grubba)  while (1) {
ce71272006-02-10Henrik Grubbström (Grubba)  int token_type = inp->peek_byte();
a451ba2006-02-09Henrik Grubbström (Grubba)  TDS_WERROR("Process row tokens: Token type: %d\n", token_type); switch(token_type) { case TDS_RESULT_TOKEN: case TDS_ROWFMT2_TOKEN: case TDS7_RESULT_TOKEN: // Some other result starts here. return 0; case TDS_ROW_TOKEN:
ce71272006-02-10Henrik Grubbström (Grubba)  inp->get_byte();
db22f42006-02-10Henrik Grubbström (Grubba)  return process_row(inp, col_info);
a451ba2006-02-09Henrik Grubbström (Grubba)  break; case TDS_DONE_TOKEN: case TDS_DONEPROC_TOKEN:
ce71272006-02-10Henrik Grubbström (Grubba)  if (!leave_end_token) inp->get_byte();
a451ba2006-02-09Henrik Grubbström (Grubba)  return 0; default:
ce71272006-02-10Henrik Grubbström (Grubba)  inp->get_byte(); process_default_tokens(inp, token_type);
a451ba2006-02-09Henrik Grubbström (Grubba)  break; } } }
9eaf1d2008-06-28Martin Nilsson  protected void process_login_tokens(InPacket inp)
a451ba2006-02-09Henrik Grubbström (Grubba)  { int ok = 0; int token_type; do {
ce71272006-02-10Henrik Grubbström (Grubba)  token_type = inp->get_byte();
a451ba2006-02-09Henrik Grubbström (Grubba)  TDS_WERROR("Got token: %d\n", token_type); switch(token_type) { case TDS_DONE_TOKEN: TDS_WERROR("TDS_DONE_TOKEN\n"); break; case TDS_AUTH_TOKEN: TDS_WERROR("TDS_AUTH_TOKEN\n");
ce71272006-02-10Henrik Grubbström (Grubba)  process_auth(inp);
a451ba2006-02-09Henrik Grubbström (Grubba)  break; case TDS_LOGINACK_TOKEN: TDS_WERROR("TDS_LOGINACK_TOKEN\n");
ce71272006-02-10Henrik Grubbström (Grubba)  int len = inp->get_smallint(); int ack = inp->get_byte(); int major = inp->get_byte(); int minor = inp->get_byte(); inp->get_smallint(); /* Ignored. */ inp->get_byte(); /* Product name len */ server_product_name = inp->get_string((len-10)/2); int product_version = inp->get_int_be();
a451ba2006-02-09Henrik Grubbström (Grubba)  if ((major == 4) && (minor == 2) && ((product_version & 0xff0000ff) == 0x5f0000ff)) { // Workaround for bugs in MSSQL 6.5 & 7.0 for TDS 4.2. product_version = (product_version & 0xffff00) | 0x800000000; } // TDS 5.0 reports 5 on success 6 on fail. // TDS 4.2 reports 1 on success and N/A on failure. if ((ack == 5) || (ack == 1)) ok = 1; TDS_WERROR(" ok:%d ack:%d %s major:%d minor:%d version:%08x\n", ok, ack, server_product_name, major, minor, product_version); server_product_name = (server_product_name/"\0")[0]; server_data = sprintf("%s %d.%d.%d.%d", server_product_name, product_version>>24, product_version & 0xffffff, major, minor); break; default:
ce71272006-02-10Henrik Grubbström (Grubba)  process_default_tokens(inp, token_type);
a451ba2006-02-09Henrik Grubbström (Grubba)  break; } } while (token_type != TDS_DONE_TOKEN); //if (!(spid = rows_affected)) set_spid(); if (!ok) tds_error("Login failed.\n"); TDS_WERROR("Login ok!\n"); }
ce71272006-02-10Henrik Grubbström (Grubba)  InPacket submit_query(compile_query query, void|array(mixed) params)
a451ba2006-02-09Henrik Grubbström (Grubba)  { Packet p = Packet();
48b40f2007-04-13Henrik Grubbström (Grubba)  string raw = query->splitted_query*"?"; p->put_raw(query->query_string, sizeof(query->query_string)); return send_packet(p, 0x01, 1); } InPacket submit_execdirect(compile_query query, void|array(mixed) params) { Packet p = Packet(); p->put_smallint(-1); p->put_smallint(10); // TDS_SP_EXECUTESQL p->put_smallint(0); p->put_smallint(0); p->put_byte(SYBNTEXT); p->put_int(sizeof(query->query_string)); p->put_raw(COLLATION, sizeof(COLLATION)); p->put_int(sizeof(query->query_string)); p->put_raw(query->query_string, sizeof(query->query_string)); int i; string param_definitions = string_to_utf16(map(params, lambda(mixed val) { return sprintf("@P%d ntext", ++i); })*","); p->put_smallint(0); p->put_byte(SYBNTEXT); p->put_int(sizeof(param_definitions)); p->put_raw(COLLATION, sizeof(COLLATION)); p->put_int(sizeof(param_definitions)||-1); p->put_raw(param_definitions, sizeof(param_definitions)); foreach(params; int i; mixed param) { p->put_data_info(param, 0); p->put_data(param, /* current_row, */ i);
a451ba2006-02-09Henrik Grubbström (Grubba)  }
48b40f2007-04-13Henrik Grubbström (Grubba)  return send_packet(p, 0x03, 1);
a451ba2006-02-09Henrik Grubbström (Grubba)  }
d8afe72006-02-15Henrik Grubbström (Grubba)  void disconnect()
a451ba2006-02-09Henrik Grubbström (Grubba)  { socket->close(); destruct(); }
9eaf1d2008-06-28Martin Nilsson  protected void create(string server, int port, string database,
a451ba2006-02-09Henrik Grubbström (Grubba)  string username, string auth) { string domain; array(string) tmp = username/"\\"; if (sizeof(tmp) > 1) { // Domain login. domain = tmp[0]; username = tmp[1..]*"\\"; }
8e06a32014-09-30Martin Nilsson  this::server = server; this::port = port; this::database = database; this::username = username; this::password = auth; this::domain = domain;
a451ba2006-02-09Henrik Grubbström (Grubba) 
5495b42006-02-13Henrik Grubbström (Grubba)  TDS_WERROR("Connecting to %s:%d with TDS version %d.%d\n",
a451ba2006-02-09Henrik Grubbström (Grubba)  server, port, major_version, minor_version); socket = Stdio.File(); if (!socket->connect(server, port)) { socket = 0; tds_error("Failed to connect to %s:%d\n", server, port); }
ce71272006-02-10Henrik Grubbström (Grubba)  process_login_tokens(send_login());
a451ba2006-02-09Henrik Grubbström (Grubba)  } } object(Connection) con; int affected_rows; string server_product_name = ""; int busy;
05aab12006-02-15Henrik Grubbström (Grubba)  void Disconnect(int|void keep_alive)
a451ba2006-02-09Henrik Grubbström (Grubba)  { con->disconnect(); con = 0;
05aab12006-02-15Henrik Grubbström (Grubba)  if (!keep_alive) { destruct(); }
a451ba2006-02-09Henrik Grubbström (Grubba)  } void Connect(string server, int port, string database, string uid, string password) { con = Connection(server, port, database, uid, password); }
26d74c2006-02-10Henrik Grubbström (Grubba) #if (__REAL_MAJOR__ > 7) || ((__REAL_MAJOR__ == 7) && (__REAL_MINOR__ >= 6))
a451ba2006-02-09Henrik Grubbström (Grubba) };
26d74c2006-02-10Henrik Grubbström (Grubba) #endif /* Pike 7.6 or later */
a451ba2006-02-09Henrik Grubbström (Grubba) 
1084be2006-02-14Henrik Grubbström (Grubba)  //! A compiled query.
a451ba2006-02-09Henrik Grubbström (Grubba) class compile_query { int n_param;
48b40f2007-04-13Henrik Grubbström (Grubba)  string query_string;
a451ba2006-02-09Henrik Grubbström (Grubba)  array(string) splitted_query; array(mixed) params; void parse_prepared_query() { // FIXME: }
9eaf1d2008-06-28Martin Nilsson  protected array(string) split_query_on_placeholders(string query)
a451ba2006-02-09Henrik Grubbström (Grubba)  { array(string) res = ({}); int i; int j = 0; for (i = 0; i < sizeof(query); i++) { switch(query[i]) { case '\'': i = search(query, "\'", i+1); if (i == -1) { TDS_WERROR("Bad quoting!\n"); i = sizeof(query); } break; case '\"': i = search(query, "\"", i+1); if (i == -1) { TDS_WERROR("Bad quoting!\n"); i = sizeof(query); } break; case '[': i = search(query, "]", i+1); if (i == -1) { TDS_WERROR("Bad quoting!\n"); i = sizeof(query); } break; case '-': if (query[i..i+1] == "--") { i = search(query, "\n", i+1); if (i == -1) { TDS_WERROR("Unterminated comment.\n"); i = sizeof(query); } } break; case '/': if (query[i..i+1] == "/*") { i = search(query, "*/", i+2); if (i == -1) { TDS_WERROR("Unterminated comment.\n"); i = sizeof(query); } } break; case '?': res += ({ query[j..i-1] }); j = i+1; break; } } res += ({ query[j..] }); return map(res, string_to_utf16); }
1084be2006-02-14Henrik Grubbström (Grubba)  //! Compile a query. //! //! @seealso //! @[big_query()]
9eaf1d2008-06-28Martin Nilsson  protected void create(string query)
a451ba2006-02-09Henrik Grubbström (Grubba)  {
9e76f02006-02-10Henrik Grubbström (Grubba)  TDS_WERROR("Compiling query: %O\n", query);
a451ba2006-02-09Henrik Grubbström (Grubba)  splitted_query = split_query_on_placeholders(query); n_param = sizeof(splitted_query)-1;
48b40f2007-04-13Henrik Grubbström (Grubba)  if (n_param) { // Insert placeholders. int i; query_string = map((splitted_query/1)*({0}), lambda(string segment) { if (segment) return segment; return string_to_utf16(sprintf("@P%d", ++i)); })*""; } else { query_string = splitted_query[0]; }
a451ba2006-02-09Henrik Grubbström (Grubba)  } }
1084be2006-02-14Henrik Grubbström (Grubba) //! A query result set.
a451ba2006-02-09Henrik Grubbström (Grubba) class big_query {
9eaf1d2008-06-28Martin Nilsson  protected int row_no; protected int eot;
a451ba2006-02-09Henrik Grubbström (Grubba) 
9eaf1d2008-06-28Martin Nilsson  protected Connection.InPacket result_packet; protected array(mapping(string:mixed)) column_info;
ce71272006-02-10Henrik Grubbström (Grubba) 
1084be2006-02-14Henrik Grubbström (Grubba)  //! Fetch the next row from the result set. //! //! @returns //! Returns @expr{0@} (zero) if all rows have been returned. //! //! Otherwise returns an @expr{array(string|int)@} with one //! entry for each field. If the field is @tt{NULL@} the //! entry will be @expr{0@} (zero), otherwise the entry //! will contain a string representing the value.
a451ba2006-02-09Henrik Grubbström (Grubba)  int|array(string|int) fetch_row() { if (eot) return 0; TDS_WERROR("fetch_row()::::::::::::::::::::::::::::::::::::::::::\n");
db22f42006-02-10Henrik Grubbström (Grubba)  int|array(string|int) res = con->process_row_tokens(result_packet, column_info);
a451ba2006-02-09Henrik Grubbström (Grubba)  eot = !res; row_no++; return res; }
1084be2006-02-14Henrik Grubbström (Grubba)  //! Fetch a description of the fields in the result. //! //! @returns //! Returns an array with a mapping for each of the fields in //! the result. //! //! The mappings contain the following information: //! @ul //! @item //! Standard fields: //! @mapping //! @member string "name" //! The name of the field. //! @member string|void "table" //! The name of the table (if available). //! @endmapping //! @item //! TDS-specific fields: //! @mapping //! @member int(0..1) "nullable" //! @expr{1@} if the field may contain @tt{NULL@}. //! @member int(0..1) "writeable" //! @expr{1@} if the field may be changed. //! @member int(0..1) "identity" //! @expr{1@} if the field is the identity for the row. //! @member int "column_size" //! Width of the field. //! @member int(0..1) "timestamp" //! Time stamp information for last change is available. //! @member int|void "column_prec" //! Precision of the field. //! @member int|void "column_scale" //! Scale exponent of the field. //! @member int "usertype" //! @member int "flags" //! @member int "column_type" //! @member int "cardinal_type" //! @member int "varint_size" //! Internal use only. //! @endmapping //! @endul
a451ba2006-02-09Henrik Grubbström (Grubba)  array(mapping(string:mixed)) fetch_fields() { TDS_WERROR("fetch_fields()::::::::::::::::::::::::::::::::::::::::::\n");
db22f42006-02-10Henrik Grubbström (Grubba)  return copy_value(column_info);
a451ba2006-02-09Henrik Grubbström (Grubba)  }
1084be2006-02-14Henrik Grubbström (Grubba)  //! Execute a query against the database. //! //! @param query //! The query to execute. This can either be a string, or //! a compiled query. //! //! @seealso //! @[compile_query()]
9eaf1d2008-06-28Martin Nilsson  protected void create(string|compile_query query)
a451ba2006-02-09Henrik Grubbström (Grubba)  { if (stringp(query)) { query = compile_query(query); }
db22f42006-02-10Henrik Grubbström (Grubba)  query->parse_prepared_query(); if (busy) { tds_error("Connection not idle.\n"); }
48b40f2007-04-13Henrik Grubbström (Grubba)  if (!query->n_param) {
db22f42006-02-10Henrik Grubbström (Grubba)  result_packet = con->submit_query(query); } else { result_packet = con->submit_execdirect(query, query->params); } column_info = con->process_result_tokens(result_packet); if (!column_info) destruct();
a451ba2006-02-09Henrik Grubbström (Grubba)  } }
9eaf1d2008-06-28Martin Nilsson protected compile_query compiled_insert_id =
5495b42006-02-13Henrik Grubbström (Grubba)  compile_query("SELECT @@identity AS insert_id");
1084be2006-02-14Henrik Grubbström (Grubba) //! Fetch the identity of the last insert (if available). //! //! This performs the query @expr{"SELECT @@@@identity AS insert_id"@}. //! //! @returns //! Returns the identity of the last insert as an integer if available. //! Otherwise returns @expr{0@} (zero).
5495b42006-02-13Henrik Grubbström (Grubba) int insert_id() { object res = big_query(compiled_insert_id); array(mapping(string:mixed)) field_info = res->fetch_fields(); if (!field_info) return 0; array(string|int) row = res->fetch_row(); if (!row) return 0; foreach(field_info->name; int i; string name) { if (name == "insert_id") return (int)row[i]; } return 0; }
1084be2006-02-14Henrik Grubbström (Grubba) //! Return a string describing the server.
a451ba2006-02-09Henrik Grubbström (Grubba) string server_info() { return server_data; }
1084be2006-02-14Henrik Grubbström (Grubba) //! Return the last error (or possibly the last warning or //! informational message).
9e76f02006-02-10Henrik Grubbström (Grubba) string error() { return last_error; }
1084be2006-02-14Henrik Grubbström (Grubba) //! Connect to a remote SQL server via the TDS protocol. //! //! @param server //! Server to connect to. //! //! @param database //! Database to connect to. //! //! @param user //! User to access as. //! //! An explicit domain may be specified by preceeding the user name //! with the domain name and a @expr{'\\'@}. //! //! @param password //! Password to access with. //! //! Usually accessed via @[Sql.Sql()]. //! //! @seealso //! @[Sql.Sql()]
9eaf1d2008-06-28Martin Nilsson protected void create(string|void server, string|void database,
0a5ec32012-04-12Henrik Grubbström (Grubba)  string|void user, string|void password, mapping|void options)
a451ba2006-02-09Henrik Grubbström (Grubba) { if (con) {
05aab12006-02-15Henrik Grubbström (Grubba)  Disconnect(1);
a451ba2006-02-09Henrik Grubbström (Grubba)  } int port = DEF_PORT; if (server) { array(string) tmp = server/":"; if (sizeof(tmp) > 1) { port = (int)tmp[-1];
48b40f2007-04-13Henrik Grubbström (Grubba)  server = tmp[..sizeof(tmp)-2]*":";
a451ba2006-02-09Henrik Grubbström (Grubba)  } } else { server = "127.0.0.1"; } Connect(server, port, database || "default",
1084be2006-02-14Henrik Grubbström (Grubba)  user || "", password || "");
26d74c2006-02-10Henrik Grubbström (Grubba) }