From a451baea0b79d942f3e3d1ddd6e43fe968cfef1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Grubbstr=C3=B6m=20=28Grubba=29?= <grubba@grubba.org> Date: Thu, 9 Feb 2006 18:18:30 +0100 Subject: [PATCH] First version. Lots of bugs left. Debug is enabled. Rev: lib/modules/Sql.pmod/tds.pike:1.1 --- .gitattributes | 1 + lib/modules/Sql.pmod/tds.pike | 1411 +++++++++++++++++++++++++++++++++ 2 files changed, 1412 insertions(+) create mode 100644 lib/modules/Sql.pmod/tds.pike diff --git a/.gitattributes b/.gitattributes index 302945976f..bfdf71a4ad 100644 --- a/.gitattributes +++ b/.gitattributes @@ -224,6 +224,7 @@ testfont binary /lib/modules/Sql.pmod/sql_result.pike foreign_ident /lib/modules/Sql.pmod/sql_util.pmod foreign_ident /lib/modules/Sql.pmod/sybase.pike foreign_ident +/lib/modules/Sql.pmod/tds.pike foreign_ident /lib/modules/Standards.pmod/ASN1.pmod/Decode.pmod foreign_ident /lib/modules/Standards.pmod/ASN1.pmod/Types.pmod foreign_ident /lib/modules/Standards.pmod/ASN1.pmod/testsuite.in foreign_ident diff --git a/lib/modules/Sql.pmod/tds.pike b/lib/modules/Sql.pmod/tds.pike new file mode 100644 index 0000000000..f8e0a00521 --- /dev/null +++ b/lib/modules/Sql.pmod/tds.pike @@ -0,0 +1,1411 @@ +/* + * $Id: tds.pike,v 1.1 2006/02/09 17:18:30 grubba Exp $ + * + * A Pike implementation of the TDS protocol. + * + * Henrik Grubbstr�m 2006-02-08. + */ + +#define TDS_DEBUG + +#ifdef TDS_DEBUG +#define TDS_WERROR(X...) werror("TDS:" + X) +#else +#define TDS_WERROR(X...) +#endif + +static int filter_noprint(int char) +{ + return ((char == 32) || ((char != 0x7f) && (char & 0x60)))?char:'.'; +} + +static string hex_dump(string data) { + 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 * ""; +} + +static { + constant DEF_MAJOR = 8; + constant DEF_MINOR = 0; + constant DEF_PORT = 1433; + constant DEF_DOMAIN = ""; + constant DEFAULT_CAPABILITIES = + "\x01\x09\x00\x00\x06\x6d\x7f\xff" + "\xff\xff\xfe\x02\x09\x00\x00\x00" + "\x00\x0a\x68\x00\x00\x00"; + 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"; + + 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 */ + }; + + 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, + }; + + enum ColType { + 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. */ + }; + + enum ResultTypes { + TDS_ROW_RESULT = 4040, + TDS_PARAM_RESULT = 4042, + TDS_STATUS_RESULT = 4043, + TDS_MSG_RESULT = 4044, + TDS_COMPUTE_RESULT = 4045, + TDS_CMD_DONE = 4046, + TDS_CMD_SUCCEED = 4047, + TDS_CMD_FAIL = 4048, + TDS_ROWFMT_RESULT = 4049, + TDS_COMPUTEFMT_RESULT = 4050, + TDS_DESCRIBE_RESULT = 4051, + TDS_DONE_RESULT = 4052, + TDS_DONEPROC_RESULT = 4053, + TDS_DONEINPROC_RESULT = 4054, + }; + + string server_data; + string last_error; + array(mapping(string:mixed)) column_info; + + void tds_error(string msg, mixed ... args) + { + if (sizeof(args)) msg = sprintf(msg, @args); + error(last_error = msg); + } + + static object utf16enc = Locale.Charset.encoder("UTF16LE"); + static string string_to_utf16(string s) + { + return utf16enc->feed(s)->drain(); + } + static object utf16dec = Locale.Charset.decoder("UTF16LE"); + static string utf16_to_string(string s) + { + return utf16dec->feed(s)->drain(); + } + + 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; + string capabilities = DEFAULT_CAPABILITIES; + + Stdio.File socket; + +#define FMT_SMALLINT "%-2c" +#define FMT_INT "%-4c" +#define FMT_INT8 "%-4c" + + int inpos = 0; + string inbuf = ""; + static void fill_buf() + { + string header = socket->read(8); + if (!header || sizeof(header) < 8) { + tds_error("Failed to read packet header: %O, %s.\n", + header, strerror(socket->errno())); + } + 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; + 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; + } + + string get_raw(int bytes) + { + while (inpos + bytes > sizeof(inbuf)) { + fill_buf(); + } + string raw = inbuf[inpos..inpos + bytes - 1]; + inpos += bytes; + return raw; + } + + string peek_raw(int bytes) + { + while (inpos + bytes > sizeof(inbuf)) { + fill_buf(); + } + string raw = inbuf[inpos..inpos + bytes - 1]; + return raw; + } + + 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]; + } + + string get_string(int len) + { + if (!len) return ""; + TDS_WERROR("get_string(%d)...\n", len); + return utf16_to_string(get_raw(len*2)); + } + + void expect(string s) + { + string r = get_raw(sizeof(s)); + if (r != s) { + tds_error("Expectation failed: Got %O, expected %O\n", + r, s); + } + } + + class Packet + { + array(string|int) segments = ({}); + array(string) strings = ({}); + int flags; + + static void create(int|void flags) + { + this_program::flags = flags; + } + + 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 }); + } + + mixed cast(string s) + { + 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); + } + } + + static void send_packet(Packet p, int flag, int|void last) + { + string raw = (string) p; + + // NOTE: Network byteorder!! + raw = + sprintf("%-1c%-1c%2c\0\0%-1c\0%s", + flag, last, + sizeof(raw) + 8, + 1, /* TDS 7 or 8. */ + (string)raw); + TDS_WERROR("Wrapped packet: %O\n%s\n", raw, hex_dump(raw)); + if (socket->write(raw) != sizeof(raw)) { + tds_error("Failed to send packet.\n" + "raw: %O\n", raw); + } + Stdio.write_file("packet.bin", raw); + } + + static string crypt_pass(string password) + { + password = string_to_utf16(password); + password ^= "\x5a"*sizeof(password); + return (string)map((array)password, + lambda(int byte) { + return ((byte >> 4) | (byte << 4)) & 0xff; + }); + } + + static void send_login() + { + 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"); + send_packet(p, 0x10, 1); + } + + string ecb_encrypt(string data, string key) + { + Crypto.DES des = Crypto.DES(); + des->set_encrypt_key(des->fix_parity(key)); + 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); + Crypto.MD4 md4 = Crypto.MD4(); + md4->update(nt_passwd); + return encrypt_answer(md4->digest() + "\0"*16, nonce); + } + + static void send_auth(string nonce) + { + 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); + } + + static void process_auth() + { + int pdu_size = get_smallint(); + if (pdu_size < 32) tds_error("Bad pdu size: %d\n", pdu_size); + expect("NTLMSSP\0"); + get_int(); /* sequence -> 2 */ + get_int(); /* domain len * 2 */ + get_int(); /* domain offset */ + get_int(); /* flags */ + string nonce = get_raw(8); + /* Discard context, target and data info */ + get_raw(pdu_size - 32); + TDS_WERROR("Got nonce: %O\n", nonce); + send_auth(nonce); + } + + static void process_msg(int token_type) + { + TDS_WERROR("TDS_ERROR_TOKEN | TDS_INFO_TOKEN | TDS_EED_TOKEN\n"); + int len = get_smallint(); + int no = get_int(); + int state = get_byte(); + int level = get_byte(); + 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; + + int state_len = get_byte(); + string state = get_raw(state_len); + has_eed = get_byte(); + /* Ignore status and transaction state */ + get_smallint(); + 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; + } + string message = get_string(get_smallint()); + string server = get_string(get_byte()); + string proc_name = get_string(get_byte()); + int line = get_smallint(); + if (has_eed) { + while (1) { + switch(peek_byte()) { + case TDS5_PARAMFMT_TOKEN: + case TDS5_PARAMFMT2_TOKEN: + case TDS5_PARAMS_TOKEN: + process_default_tokens(get_byte()); + continue; + } + break; + } + } + if (is_error) { + tds_error("%d: %s:%s:%d %s\n", + level, proc_name, server, line, message); + } else { + TDS_WERROR("%d: %s:%s:%d %s\n", + level, proc_name, server, line, message); + } + } + + static void process_env_chg() + { + int size = get_smallint(); + int env_type = get_byte(); + if (env_type == TDS_ENV_SQLCOLLATION) { + size = get_byte(); + string block = get_raw(size); + if (size >= 5) { + string collation = block[..4]; + int lcid; + sscanf(collation, "%-3c", lcid); + /* FIXME: Should we care? */ + } + /* Discard old collation. */ + get_raw(get_byte()); + return; + } + string new_val = get_string(get_byte()); + string old_val = get_string(get_byte()); + 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) + { + 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); + } + + mapping(string:mixed) tds7_get_data_info() + { + mapping(string:mixed) res = ([ + "usertype":get_smallint(), + "flags":get_smallint(), + "column_type":get_byte(), + ]); + 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); + TDS_WERROR("Intermediate info: %O\n", res); + switch(res->varint_size) { + case 0: + res->column_size = get_size_by_type(res->column_type); + break; + case 4: + res->column_size = get_int(); + break; + case 2: + res->column_size = get_smallint(); + break; + case 1: + res->column_size = get_byte(); + break; + } + TDS_WERROR("Column size: %d bytes\n", res->column_size); + + res->timestamp = (res->cardinal_type == SYBBINARY) || + (res->cardinal_type == TDS_UT_TIMESTAMP); + + if (is_numeric_type(res->cardinal_type)) { + TDS_WERROR("is_numeric\n"); + res->column_prec = get_byte(); + res->column_scale = get_byte(); + } + if (is_collate_type(res->column_type)) { + TDS_WERROR("is_collate\n"); + get_raw(4); // Collation + get_byte(); // charset + } + if (is_blob_type(res->cardinal_type)) { + TDS_WERROR("is_blob\n"); + res->table = get_string(get_smallint()); + TDS_WERROR("Table name: %O\n", res->table); + } + res->name = get_string(get_byte()); + TDS_WERROR("Column info: %O\n", res); + return res; + } + + static void tds7_process_result() + { + int num_cols = get_smallint(); + if (num_cols == 0xffff) { + TDS_WERROR("No meta data.\n"); + column_info = 0; + return; + } + TDS_WERROR("%d columns in result.\n", num_cols); + column_info = allocate(num_cols); + busy = 1; + int offset = 0; + foreach(column_info; int col; ) { + column_info[col] = tds7_get_data_info(); + column_info[col]->column_offset = offset; + TDS_WERROR("Offset: 0x%04x\n", offset); + if (is_numeric_type(column_info[col]->cardinal_type)) { + offset += 35; // sizeof(TDS_NUMERIC) + } else if (is_blob_type(column_info[col]->cardinal_type)) { + offset += 32; // sizeof(TDSBLOB) + } else { + offset += column_info[col]->column_size; + } + offset += (~(offset - 1) & 0x3); // Align. + TDS_WERROR("Next offset: 0x%04x\n", offset); + } + + } + + static void process_default_tokens(int token_type) + { + 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: + process_msg(token_type); + return; + case TDS_ENVCHANGE_TOKEN: + process_env_chg(); + return; + case TDS7_RESULT_TOKEN: + tds7_process_result(); + break; + } + } + + int process_result_tokens() + { + if (!busy) { + //return TDS_NO_MORE_RESULTS; + } + while (1) { + int token_type = peek_byte(); + TDS_WERROR("Got result token %d\n", token_type); + switch(token_type) { + case TDS7_RESULT_TOKEN: + TDS_WERROR("TDS7_RESULT_TOKEN\n"); + get_byte(); + tds7_process_result(); + if (peek_byte() == TDS_TABNAME_TOKEN) { + TDS_WERROR("TDS_TABNAME_TOKEN\n"); + process_default_tokens(get_byte()); + if (peek_byte() == TDS_COLINFO_TOKEN) { + TDS_WERROR("TDS_COLINFO_TOKEN\n"); + get_byte(); + //process_colinfo(); // FIXME! + } + } + TDS_WERROR("==> TDS_ROWFMT_RESULT\n"); + return TDS_ROWFMT_RESULT; + case TDS_ROW_TOKEN: + TDS_WERROR("==> TDS_ROW_RESULT\n"); + return TDS_ROW_RESULT; + default: + get_byte(); + TDS_WERROR("==> FIXME: process_result_tokens\n"); + return 0; /***** FIXME:::::: *****/ + break; + } + } + } + + static string|int get_data(mapping(string:mixed) info, int col) + { + 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: + int sz = get_int(); + if (!sz) return 0; // NULL; + return get_raw(sz); + case SYBLONGBINARY: + colsize = get_int(); + break outer; + } + int len = get_byte(); + if (len == 16) { + blobinfo = ([ + "textptr":get_raw(16), + "timestamp":get_raw(8), + ]); + colsize = get_int(); + TDS_WERROR("BLOB: size:%d info:%O\n", colsize, blobinfo); + } + break; + case 2: + colsize = get_smallint(); + if (!colsize) { + // Empty string. + return ""; + } + if (colsize == 0xffff) colsize = 0; + break; + 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 + string arr = get_raw(colsize); + TDS_WERROR("NUMERIC: %O\n", arr); + return arr; // FIXME + } else if (is_blob_type(info->cardinal_type)) { + // BLOB + string raw = get_raw(colsize); + TDS_WERROR("BLOB. colsize:%d, raw: %O\n", colsize, raw); + if (is_char_type(info->cardinal_type)) { + return utf16_to_string(raw); + } + return raw; + } else { + string raw = get_raw(colsize); + TDS_WERROR("Got raw data: %O\ncolumn_size:%d colsize:%d\n%s\n", + raw, info->column_size, colsize, hex_dump(raw)); + 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; + } + } + + static string|int convert(string|int raw, mapping(string:mixed) info) + { + switch(info->cardinal_type) { + 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: + return raw; + case SYBMONEY4: + { + int val; + sscanf(raw, "%-4c", val); + if (val < 0) { + int cents = -(val/50 - 1)/2; + return sprintf("-%d.%02d", cents/100, cents%100); + } else { + int cents = (val/50 + 1)/2; + return sprintf("%d.%02d", cents/100, cents%100); + } + } + case SYBMONEY: + { + int val; + string sgn = ""; + sscanf(raw, "%-8c", val); + if (val < 0) { + sgn = "-"; + val = -val; + } + int cents = (val + 50)/100; + return sprintf("%s%d.%02d", sgn, cents/100, cents%100); + } + case SYBNUMERIC: + case SYBDECIMAL: + case SYBREAL: + case SYBFLT8: + case SYBUNIQUE: + default: + // FIXME: + TDS_WERROR("Not yet supported: %d\n", info->cardinal_type); + return raw; + case SYBBIT: + case SYBBITN: + return raw[0]?"1":"0"; + case SYBINT1: + // FIXME: SIGN! + return sprintf("%d", array_sscanf(raw, "%-1c")[0]); + case SYBINT2: + // FIXME: SIGN! + return sprintf("%d", array_sscanf(raw, "%-2c")[0]); + case SYBINT4: + // FIXME: SIGN! + return sprintf("%d", array_sscanf(raw, "%-4c")[0]); + case SYBINT8: + // FIXME: SIGN! + return sprintf("%d", array_sscanf(raw, "%-8c")[0]); + case SYBDATETIME: + case SYBDATETIME4: + { + int day, min, sec, sec_300; + if (info->cardinal_type == SYBDATETIME) { + 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++; + return sprintf("%04d-%02d-%02dT%02d:%02d:%02d", + year, mon, mday, hour, min, sec); + } + } + } + + static array(string|int) process_row() + { + if (!column_info) return 0; + array(string|int) res = allocate(sizeof(column_info)); + foreach(column_info; int i; mapping(string:mixed) info) { + res[i] = convert(get_data(info, i), info); + } + return res; + } + + array(string|int) process_row_tokens(int|void leave_end_token) + { + if (!busy) return 0; // No more rows. + while (1) { + int token_type = peek_byte(); + 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. + busy = 0; + return 0; + case TDS_ROW_TOKEN: + get_byte(); + return process_row(); + break; + case TDS_DONE_TOKEN: + case TDS_DONEPROC_TOKEN: + case TDS_DONEINPROC_TOKEN: + if (!leave_end_token) get_byte(); + busy = 0; + return 0; + default: + get_byte(); + process_default_tokens(token_type); + break; + } + } + } + + static void process_login_tokens() + { + int ok = 0; + int token_type; + do { + token_type = get_byte(); + 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"); + process_auth(); + break; + case TDS_LOGINACK_TOKEN: + TDS_WERROR("TDS_LOGINACK_TOKEN\n"); + int len = get_smallint(); + int ack = get_byte(); + int major = get_byte(); + int minor = get_byte(); + get_smallint(); /* Ignored. */ + get_byte(); /* Product name len */ + server_product_name = get_string((len-10)/2); + int product_version = get_int_be(); + 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: + process_default_tokens(token_type); + 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"); + } + + void submit_query(compile_query query, void|array(mixed) params) + { + Packet p = Packet(); + if (!query->n_param || !params || !sizeof(params)) { + string raw = query->splitted_query*"?"; + p->put_raw(raw, sizeof(raw)); + send_packet(p, 0x01, 1); + } else { + tds_error("parametrized queries not supported yet.\n"); + } + } + + static void disconnect() + { + socket->close(); + destruct(); + } + + static void create(string server, int port, string database, + string username, string auth) + { + string domain; + array(string) tmp = username/"\\"; + if (sizeof(tmp) > 1) { + // Domain login. + domain = tmp[0]; + username = tmp[1..]*"\\"; + } + + this_program::server = server; + this_program::port = port; + this_program::database = database; + this_program::username = username; + this_program::password = auth; + this_program::domain = domain; + + TDS_WERROR("Connecting to %s:%d version %d.%d\n", + 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); + } + send_login(); + process_login_tokens(); + } + } + + object(Connection) con; + + int affected_rows; + string server_product_name = ""; + + int busy; + + void Disconnect() + { + con->disconnect(); + con = 0; + } + + void Connect(string server, int port, string database, + string uid, string password) + { + con = Connection(server, port, database, uid, password); + } + + void Execute(compile_query query) + { + query->parse_prepared_query(); + if (busy) { + tds_error("Connection not idle.\n"); + } + if (!query->params) { + con->submit_query(query); + } else { + con->submit_execdirect(query, query->params); + } + int done = 0; + int res_type; + while (!done) { + res_type = con->process_result_tokens(); + switch(res_type) { + case TDS_COMPUTE_RESULT: + case TDS_ROW_RESULT: + done = 1; + break; + default: + TDS_WERROR("res_type: %d\n", res_type); + break; + } + } + // populate_ird + switch(res_type) { + //case TDS_NO_MORE_RESULTS: + } + } +}; + +class compile_query +{ + int n_param; + array(string) splitted_query; + + array(mixed) params; + + void parse_prepared_query() + { + // FIXME: + } + + static array(string) split_query_on_placeholders(string query) + { + 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); + } + + static void create(string query) + { + splitted_query = split_query_on_placeholders(query); + n_param = sizeof(splitted_query)-1; + } +} + +class big_query +{ + static int row_no; + static int eot; + + int|array(string|int) fetch_row() + { + if (eot) return 0; + TDS_WERROR("fetch_row()::::::::::::::::::::::::::::::::::::::::::\n"); + int|array(string|int) res = con->process_row_tokens(); + eot = !res; + row_no++; + return res; + } + + array(mapping(string:mixed)) fetch_fields() + { + TDS_WERROR("fetch_fields()::::::::::::::::::::::::::::::::::::::::::\n"); + return copy_value(column_info); + } + + static void create(string|compile_query query) + { + if (stringp(query)) { + query = compile_query(query); + } + Execute(query); + } +} + +string server_info() +{ + return server_data; +} + +static void create(string|void server, string|void database, + string|void user, string|void pwd) +{ + if (con) { + Disconnect(); + } + int port = DEF_PORT; + if (server) { + array(string) tmp = server/":"; + if (sizeof(tmp) > 1) { + port = (int)tmp[-1]; + server = tmp[..sizeof(tmp)-2]*":"; + } + } else { + server = "127.0.0.1"; + } + Connect(server, port, database || "default", + user || "", pwd || ""); +} \ No newline at end of file -- GitLab