diff --git a/src/Makefile.am b/src/Makefile.am index 27a828cd5..52246d5b6 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,10 +1,5 @@ -SUBDIRS = utils replacements tds ctlib dblib -DIST_SUBDIRS = utils replacements tds ctlib dblib \ - odbc server pool apps - -if ODBC -SUBDIRS += odbc -endif +SUBDIRS = utils replacements tds +DIST_SUBDIRS = utils replacements tds server pool ctlib dblib odbc apps if INCPOOL SUBDIRS += server pool @@ -14,6 +9,12 @@ SUBDIRS += server endif endif +SUBDIRS += ctlib dblib + +if ODBC +SUBDIRS += odbc +endif + if INCAPPS SUBDIRS += apps endif diff --git a/src/odbc/unittests/.gitignore b/src/odbc/unittests/.gitignore index 7fd000752..bf6401b0c 100644 --- a/src/odbc/unittests/.gitignore +++ b/src/odbc/unittests/.gitignore @@ -87,3 +87,4 @@ /qn /connection_string_parse /tvp +/tokens diff --git a/src/odbc/unittests/CMakeLists.txt b/src/odbc/unittests/CMakeLists.txt index 74101b1b1..cde237952 100644 --- a/src/odbc/unittests/CMakeLists.txt +++ b/src/odbc/unittests/CMakeLists.txt @@ -30,7 +30,7 @@ set(tests all_types utf8_3 empty_query transaction3 transaction4 utf8_4 qn connection_string_parse - tvp + tvp tokens ) if(WIN32) diff --git a/src/odbc/unittests/Makefile.am b/src/odbc/unittests/Makefile.am index 761b925c1..80ba3f998 100644 --- a/src/odbc/unittests/Makefile.am +++ b/src/odbc/unittests/Makefile.am @@ -83,6 +83,7 @@ TESTS = \ qn$(EXEEXT) \ connection_string_parse$(EXEEXT) \ tvp$(EXEEXT) \ + tokens$(EXEEXT) \ $(NULL) check_PROGRAMS = $(TESTS) oldpwd$(EXEEXT) @@ -180,6 +181,8 @@ qn_SOURCES = qn.c connection_string_parse_SOURCES = connection_string_parse.c connection_string_parse_CPPFLAGS = $(GLOBAL_CPPFLAGS) connection_string_parse_LDFLAGS = -static ../libtdsodbc.la ../../tds/unittests/libcommon.a -shared $(GLOBAL_LD_ADD) +tokens_SOURCES = tokens.c +tokens_LDADD = libcommon.a $(ODBC_LDFLAGS) ../../replacements/libreplacements.la ../../server/libtdssrv.la $(GLOBAL_LD_ADD) noinst_LIBRARIES = libcommon.a libcommon_a_SOURCES = common.c common.h c2string.c parser.c parser.h \ diff --git a/src/odbc/unittests/tokens.c b/src/odbc/unittests/tokens.c new file mode 100644 index 000000000..bbf607fa4 --- /dev/null +++ b/src/odbc/unittests/tokens.c @@ -0,0 +1,447 @@ +#include "common.h" + +#include + +#if HAVE_UNISTD_H +#include +#endif /* HAVE_UNISTD_H */ + +#include + +#if HAVE_ERRNO_H +#include +#endif /* HAVE_ERRNO_H */ + +#if HAVE_SYS_SOCKET_H +#include +#endif /* HAVE_SYS_SOCKET_H */ + +#if HAVE_NETINET_IN_H +#include +#endif /* HAVE_NETINET_IN_H */ + +#include +#include +#include + +#include "fake_thread.h" +#include "parser.h" + +#if TDS_HAVE_MUTEX + +/* Check some internal token behaviour */ + +#ifdef _WIN32 +#define SHUT_RDWR SD_BOTH +#endif + +static void parse_sql(TDSSOCKET * tds, const char *sql); + +static void +setup_override(void) +{ + char buf[128]; + FILE *f; + + sprintf(buf, "tokens_pwd.%d", (int) getpid()); + f = fopen(buf, "w"); + assert(f); + fprintf(f, "UID=guest\nPWD=sybase\nSRV=%s\nDB=tempdb\n", odbc_server); + fclose(f); + rename(buf, "tokens_pwd"); + unlink(buf); + setenv("TDSPWDFILE", "tokens_pwd", 1); + unsetenv("TDSINIOVERRIDE"); + + unsetenv("TDSHOST"); + unsetenv("TDSPORT"); + unsetenv("TDSVER"); +} + +static TDSSOCKET * +tds_from_sock(TDSCONTEXT *ctx, TDS_SYS_SOCKET fd) +{ + TDSSOCKET *tds; + + tds = tds_alloc_socket(ctx, 4096); + if (!tds) { + CLOSESOCKET(fd); + fprintf(stderr, "out of memory"); + return NULL; + } + tds_set_s(tds, fd); + tds->out_flag = TDS_LOGIN; + /* TODO proper charset */ + tds_iconv_open(tds->conn, "ISO8859-1", 0); + /* get_incoming(tds->s); */ + tds->state = TDS_IDLE; + + tds->conn->client_spid = 0x33; + tds->conn->product_version = TDS_MS_VER(10, 0, 6000); + + return tds; +} + +static void handle_one(TDS_SYS_SOCKET sock); + +/* accept a socket and emulate a server */ +TDS_THREAD_PROC_DECLARE(fake_thread_proc, arg) +{ + TDS_SYS_SOCKET s = TDS_PTR2INT(arg), sock; + socklen_t len; + struct sockaddr_in sin; + struct pollfd fd; + + for (;;) { + fd.fd = s; + fd.events = POLLIN; + fd.revents = 0; + if (poll(&fd, 1, 30000) <= 0) { + fprintf(stderr, "poll: %d\n", sock_errno); + exit(1); + } + + memset(&sin, 0, sizeof(sin)); + len = sizeof(sin); + if (TDS_IS_SOCKET_INVALID(sock = tds_accept(s, (struct sockaddr *) &sin, &len))) { + perror("accept"); + exit(1); + } + handle_one(sock); + } + CLOSESOCKET(s); + + return TDS_THREAD_RESULT(0); +} + +static void +handle_one(TDS_SYS_SOCKET sock) +{ + TDSSOCKET *tds; + TDSLOGIN *login; + TDSCONTEXT *ctx; + + ctx = tds_alloc_context(NULL); + if (!ctx) + exit(1); + + tds = tds_from_sock(ctx, sock); + if (!tds) + exit(1); + + login = tds_alloc_read_login(tds); + if (!login) { + fprintf(stderr, "Error reading login\n"); + exit(1); + } + + if (!strcmp(tds_dstr_cstr(&login->user_name), "guest") && !strcmp(tds_dstr_cstr(&login->password), "sybase")) { + tds->out_flag = TDS_REPLY; + tds_env_change(tds, TDS_ENV_DATABASE, "master", "pubs2"); + if (IS_TDS71_PLUS(tds->conn)) + tds_put_n(tds, "\xe3\x08\x00\x07\x05\x09\x04\xd0\x00\x34\x00", 11); + tds_send_msg(tds, 5701, 2, 10, "Changed database context to 'pubs2'.", "JDBC", NULL, 1); + if (!login->suppress_language) { + tds_env_change(tds, TDS_ENV_LANG, NULL, "us_english"); + tds_send_msg(tds, 5703, 1, 10, "Changed language setting to 'us_english'.", "JDBC", NULL, 1); + } + if (IS_TDS50(tds->conn)) + tds_env_change(tds, TDS_ENV_PACKSIZE, NULL, "512"); + tds_send_login_ack(tds, "Microsoft SQL Server"); + if (!IS_TDS50(tds->conn)) + tds_env_change(tds, TDS_ENV_PACKSIZE, "4096", "4096"); + if (IS_TDS50(tds->conn)) + tds_send_capabilities_token(tds); + tds_send_done_token(tds, TDS_DONE_FINAL, 0); + } else { + exit(1); + } + tds_flush_packet(tds); + tds_free_login(login); + login = NULL; + + for (;;) { + const char *query; + + query = tds_get_generic_query(tds); + if (!query) + break; + printf("query : %s\n", query); + + tds->out_flag = TDS_REPLY; + + if (strncmp(query, "use tempdb", 10) == 0) { + tds_env_change(tds, TDS_ENV_DATABASE, "pubs2", "tempdb"); + if (IS_TDS71_PLUS(tds->conn)) + tds_put_n(tds, "\xe3\x08\x00\x07\x05\x09\x04\xd0\x00\x34\x00", 11); + tds_send_msg(tds, 5701, 2, 10, "Changed database context to 'tempdb'.", "JDBC", NULL, 1); + tds_send_done_token(tds, TDS_DONE_FINAL, 0); + tds_flush_packet(tds); + continue; + } + + parse_sql(tds, query); + continue; + if (strncmp(query, "XXX", 3) == 0) { + tds_send_done_token(tds, TDS_DONE_FINAL, 0); + tds_flush_packet(tds); + continue; + } + shutdown(sock, SHUT_RDWR); + } + tds_close_socket(tds); + tds_free_socket(tds); + tds_free_context(ctx); +} + +static char * +read_line(void *param, char *s, size_t size) +{ + const char **const p = (const char **) param; + const char *start = *p, *end; + size_t len; + + if (!*start) + return NULL; + end = strchr(start, '\n'); + end = end ? end + 1 : strchr(start, 0); + len = end - start; + if (len >= size) { + fprintf(stderr, "Line too long\n"); + exit(1); + } + memcpy(s, start, len); + s[len] = 0; + *p = start + len; + return s; +} + +static void +parse_sql(TDSSOCKET *tds, const char *sql) +{ + bool cond = true; + odbc_parser *parser = odbc_init_parser_func(read_line, &sql); + int done_token; + TDSRESULTINFO *resinfo; + TDSCOLUMN *col; + + resinfo = tds_alloc_results(1); + if (!resinfo) { + fprintf(stderr, "out of memory"); + exit(1); + } + col = resinfo->columns[0]; + tds_set_column_type(tds->conn, col, SYBVARCHAR); + col->column_size = 30; + col->on_server.column_size = 30; + if (!tds_dstr_copy(&col->column_name, "name")) + exit(1); + col->column_cur_size = 8; + col->column_data = (void *) "row data"; + + for (;;) { + char *p; + const char *cmd = odbc_get_cmd_line(parser, &p, &cond); + + if (!cmd) + break; + + if (!strcmp(cmd, "info")) { + if (!cond) + continue; + + /* RAISERROR */ + tds_send_msg(tds, 50000, 1, 5, "information message", "JDBC", NULL, 1); + continue; + } + + if (!strcmp(cmd, "error")) { + if (!cond) + continue; + + /* division by zero */ + tds_send_err(tds, 8134, 1, 16, "division by zero", "JDBC", NULL, 1); + continue; + } + + if (!strcmp(cmd, "rowfmt")) { + if (!cond) + continue; + + tds_send_table_header(tds, resinfo); + continue; + } + + if (!strcmp(cmd, "row")) { + if (!cond) + continue; + + tds_send_row(tds, resinfo); + continue; + } + + if (!strcmp(cmd, "quit")) { + if (!cond) + continue; + shutdown(tds->conn->s, SHUT_RDWR); + break; + } + + if (!strcmp(cmd, "done")) + done_token = TDS_DONE_TOKEN; + else if (!strcmp(cmd, "doneinproc")) + done_token = TDS_DONEINPROC_TOKEN; + else + done_token = 0; + if (done_token) { + const char *tok = odbc_get_tok(&p); + TDS_SMALLINT flags = TDS_DONE_MORE_RESULTS; + int rows; + + while (tok && !strcmp(tok, "final")) { + flags &= ~TDS_DONE_MORE_RESULTS; + tok = odbc_get_tok(&p); + } + + if (!cond) + continue; + + rows = tok ? atoi(tok) : -1; + if (rows >= 0) + flags |= TDS_DONE_COUNT; + else + rows = 0; + tds_send_done(tds, done_token, flags, rows); + continue; + } + odbc_fatal(parser, ": unknown command\n"); + } + tds_free_results(resinfo); + odbc_free_parser(parser); + tds_flush_packet(tds); +} + +/* + * Fetch tests: + * All reply starts with ROWFMT + ROW, we test behaviour or fetch: + * - is there a row? + * - is there an error or info? + * - is there row count and how much + */ +static void +test_fetch(const char *replies, const char *expected_no_row, const char *expected_row, int line) +{ + int ret; + char *sql; + ODBC_BUF *odbc_buf = NULL; + int expected_rows = -1; + char state[12] = "No"; + SQLLEN char_data_ind; + char char_data[64]; + + printf("Testing SQLFetch, line %d\n", line); + + CHKBindCol(1, SQL_C_CHAR, char_data, sizeof(char_data), &char_data_ind, "S"); + + ret = asprintf(&sql, "rowfmt\nrow\n%s", replies); + assert(ret >= 0); + + CHKExecDirect(T(sql), SQL_NTS, "S"); + CHKFetch("S"); + sscanf(expected_no_row, " %10s %d", state, &expected_rows); + CHKFetch(state); + ODBC_CHECK_ROWS(expected_rows); + while (CHKMoreResults("SENo") != SQL_NO_DATA) + continue; + free(sql); + + ret = asprintf(&sql, "rowfmt\nrow\nrow\n%s", replies); + assert(ret >= 0); + + CHKExecDirect(T(sql), SQL_NTS, "S"); + CHKFetch("S"); + strcpy(state, "S"); + expected_rows = -1; + sscanf(expected_row, " %10s %d", state, &expected_rows); + CHKFetch(state); + ODBC_CHECK_ROWS(expected_rows); + while (CHKMoreResults("SIENo") != SQL_NO_DATA) + continue; + free(sql); + + odbc_reset_statement(); + ODBC_FREE(); +} +#define test_fetch(r, e1, e2) test_fetch(r, e1, e2, __LINE__) + +int +main(void) +{ + int port; + char connect[100]; + + tds_socket_init(); + + for (port = 12340; port < 12350; ++port) + if (init_fake_server(port)) + break; + if (port == 12350) { + fprintf(stderr, "Cannot bind to a port\n"); + return 1; + } + printf("Fake server bound at port %d\n", port); + + odbc_read_login_info(); + setup_override(); + + odbc_use_version3 = 1; + sprintf(connect, "SERVER=127.0.0.1,%d;TDS_Version=7.3;UID=guest;PWD=sybase;DATABASE=tempdb;Encrypt=No;", port); + odbc_conn_additional_params = connect; + odbc_connect(); + + /* + * Test cases for SQLFetch (with and without row) + */ + + /* info + done with row */ + test_fetch("info\ndone 1\ndone final 2", "No 1", ""); + + /* error + done with row */ + test_fetch("error\ndone 1\ndone final", "E 1", ""); + + /* info + doneinproc with row */ + test_fetch("info\ndoneinproc 1\ndone final", "No 1", ""); + + /* error + doneinproc with row */ + test_fetch("error\ndoneinproc 1\ndoneinproc 2\ndone final", "E 1", ""); + + /* doneinproc with row + doneinproc with different row */ + test_fetch("doneinproc 1\ndoneinproc 2\ndone final", "No 1", "S 1"); + + /* doneinproc with row + done with different row */ + test_fetch("doneinproc 1\ndone 2\ndone final", "No 1", "S 1"); + + /* done with row + done with different row */ + test_fetch("done 1\ndone 2\ndone final", "No 1", "S 1"); + + odbc_disconnect(); + + odbc_connect(); + CHKExecDirect(T("done final"), SQL_NTS, "SNo"); + CHKMoreResults("No"); + CHKExecDirect(T("done final"), SQL_NTS, "SNo"); + CHKMoreResults("No"); + odbc_disconnect(); + + return 0; +} + +#else /* !TDS_HAVE_MUTEX */ +int +main(void) +{ + printf("Not possible for this platform.\n"); + odbc_test_skipped(); + return 0; +} +#endif diff --git a/src/server/login.c b/src/server/login.c index 2d05052e0..617ec32b0 100644 --- a/src/server/login.c +++ b/src/server/login.c @@ -201,7 +201,7 @@ tds7_read_login(TDSSOCKET * tds, TDSLOGIN * login) /* sql type (byte) + flag3 (byte) + timezone (int) + collation (4 byte) */ tds_get_n(tds, NULL, 10); - packet_start = IS_TDS72_PLUS(tds->conn) ? 86 + 8 : 86; /* ? */ + packet_start = IS_TDS72_PLUS(login) ? 86 + 8 : 86; /* ? */ if (packet_len < packet_start) return 0; @@ -293,6 +293,8 @@ tds7_read_login(TDSSOCKET * tds, TDSLOGIN * login) login->block_size = 0; /*0 block size for TDS 7.0 */ login->encryption_level = TDS_ENCRYPTION_OFF; + tds->conn->tds_version = login->tds_version; + return res; } diff --git a/src/server/server.c b/src/server/server.c index bab0cd01d..ef022e5b9 100644 --- a/src/server/server.c +++ b/src/server/server.c @@ -456,7 +456,7 @@ tds_send_row(TDSSOCKET * tds, TDSRESULTINFO * resinfo) void tds71_send_prelogin(TDSSOCKET * tds) { - static const unsigned char prelogin[] = { + static const unsigned char prelogin_default[] = { 0x00, 0x00, 0x1a, 0x00, 0x06, 0x01, 0x00, 0x20, 0x00, 0x01, 0x02, 0x00, 0x21, 0x00, 0x01, @@ -468,7 +468,10 @@ tds71_send_prelogin(TDSSOCKET * tds) 0x00, 0x00 }; + unsigned char prelogin[sizeof(prelogin_default)]; + memcpy(prelogin, prelogin_default, sizeof(prelogin)); + TDS_PUT_A4BE(&prelogin[0x1a], tds->conn->product_version & 0x7fffffffu); tds_put_n(tds, prelogin, sizeof(prelogin)); } diff --git a/src/server/unittest.c b/src/server/unittest.c index 99ee5e322..2518d01af 100644 --- a/src/server/unittest.c +++ b/src/server/unittest.c @@ -49,6 +49,7 @@ main(int argc, char **argv) TDSLOGIN *login; TDSRESULTINFO *resinfo; TDSCOLUMN *col; + const char *query; if (argc < 2 || atoi(argv[1]) <= 0) { fprintf(stderr, "syntax: %s \n", argv[0]); @@ -59,6 +60,10 @@ main(int argc, char **argv) tds = tds_listen(ctx, atoi(argv[1])); if (!tds) return 1; + + tds->conn->client_spid = 0x33; + tds->conn->product_version = TDS_MS_VER(10, 0, 6000); + /* get_incoming(tds->s); */ login = tds_alloc_read_login(tds); if (!login) { @@ -67,10 +72,11 @@ main(int argc, char **argv) } dump_login(login); /* tds->conn->tds_version = 0x702; */ - /* tds->conn->product_version = TDS_MS_VER(10, 0, 6000); */ if (!strcmp(tds_dstr_cstr(&login->user_name), "guest") && !strcmp(tds_dstr_cstr(&login->password), "sybase")) { tds->out_flag = TDS_REPLY; tds_env_change(tds, TDS_ENV_DATABASE, "master", "pubs2"); + if (IS_TDS71_PLUS(tds->conn)) + tds_put_n(tds, "\xe3\x08\x00\x07\x05\x09\x04\xd0\x00\x34\x00", 11); tds_send_msg(tds, 5701, 2, 10, "Changed database context to 'pubs2'.", "JDBC", "ZZZZZ", 1); if (!login->suppress_language) { tds_env_change(tds, TDS_ENV_LANG, NULL, "us_english"); @@ -79,7 +85,7 @@ main(int argc, char **argv) if (IS_TDS50(tds->conn)) tds_env_change(tds, TDS_ENV_PACKSIZE, NULL, "512"); /* TODO set mssql if tds7+ */ - tds_send_login_ack(tds, "sql server"); + tds_send_login_ack(tds, "Microsoft SQL Server"); if (!IS_TDS50(tds->conn)) tds_env_change(tds, TDS_ENV_PACKSIZE, "4096", "4096"); if (IS_TDS50(tds->conn)) @@ -93,7 +99,20 @@ main(int argc, char **argv) tds_free_login(login); login = NULL; /* printf("incoming packet %d\n", tds_read_packet(tds)); */ - printf("query : %s\n", tds_get_generic_query(tds)); + query = tds_get_generic_query(tds); + if (query != NULL && strncmp(query, "use tempdb", 10) == 0) { + printf("query : %s\n", query); + tds->out_flag = TDS_REPLY; + tds_env_change(tds, TDS_ENV_DATABASE, "pubs2", "tempdb"); + if (IS_TDS71_PLUS(tds->conn)) + tds_put_n(tds, "\xe3\x08\x00\x07\x05\x09\x04\xd0\x00\x34\x00", 11); + tds_send_msg(tds, 5701, 2, 10, "Changed database context to 'tempdb'.", "JDBC", "ZZZZZ", 1); + tds_send_done_token(tds, TDS_DONE_FINAL, 0); + tds_flush_packet(tds); + + query = tds_get_generic_query(tds); + } + printf("query : %s\n", query); tds->out_flag = TDS_REPLY; resinfo = tds_alloc_results(1); if (!resinfo) { diff --git a/src/tds/login.c b/src/tds/login.c index b373a9cc4..d35aa4508 100644 --- a/src/tds/login.c +++ b/src/tds/login.c @@ -1020,7 +1020,7 @@ tds7_send_login(TDSSOCKET * tds, const TDSLOGIN * login) static const char ext_data[] = "\x0a\x01\x00\x00\x00\x01" /* Enable UTF-8 */ "\xff"; - size_t ext_len = IS_TDS74_PLUS(tds->conn) ? sizeof(ext_data) - 1 : 0; + size_t ext_len = IS_TDS74_PLUS(login) ? sizeof(ext_data) - 1 : 0; /* fields */ enum { @@ -1044,7 +1044,7 @@ tds7_send_login(TDSSOCKET * tds, const TDSLOGIN * login) tds->out_flag = TDS7_LOGIN; - current_pos = packet_size = IS_TDS72_PLUS(tds->conn) ? 86 + 8 : 86; /* ? */ + current_pos = packet_size = IS_TDS72_PLUS(login) ? 86 + 8 : 86; /* ? */ /* check ntlm */ #ifdef HAVE_SSPI @@ -1102,7 +1102,7 @@ tds7_send_login(TDSSOCKET * tds, const TDSLOGIN * login) SET_FIELD_DSTR(LANGUAGE, login->language, 128); SET_FIELD_DSTR(DATABASE_NAME, login->database, 128); SET_FIELD_DSTR(DB_FILENAME, login->db_filename, 260); - if (IS_TDS72_PLUS(tds->conn) && login->use_new_password) { + if (IS_TDS72_PLUS(login) && login->use_new_password) { option_flag3 |= TDS_CHANGE_PASSWORD; SET_FIELD_DSTR(NEW_PASSWORD, login->new_password, 128); } @@ -1198,11 +1198,11 @@ tds7_send_login(TDSSOCKET * tds, const TDSLOGIN * login) tds_put_byte(tds, option_flag2); - if (login->readonly_intent && IS_TDS71_PLUS(tds->conn)) + if (login->readonly_intent && IS_TDS71_PLUS(login)) sql_type_flag |= TDS_READONLY_INTENT; tds_put_byte(tds, sql_type_flag); - if (IS_TDS73_PLUS(tds->conn)) + if (IS_TDS73_PLUS(login)) option_flag3 |= TDS_UNKNOWN_COLLATION_HANDLING; tds_put_byte(tds, option_flag3); @@ -1254,7 +1254,7 @@ tds7_send_login(TDSSOCKET * tds, const TDSLOGIN * login) /* db file */ PUT_STRING_FIELD_PTR(DB_FILENAME); - if (IS_TDS72_PLUS(tds->conn)) { + if (IS_TDS72_PLUS(login)) { /* new password */ PUT_STRING_FIELD_PTR(NEW_PASSWORD);