Skip to content

Commit 1786c44

Browse files
authored
Fix: Babelfish: Connection with -U/-d flags fails for truncated identifiers (#4837)
Fix TDS login and database context handling for identifiers that exceed PostgreSQL NAMEDATALEN (64 chars). TSQL allows up to 128 character login and database names which are truncated using clip + MD5 hash. Add JDBC test coverage for long login names (lowercase, mixed case, multibyte, boundary), long database names, and combined scenarios. Each database is created and dropped individually to avoid connection conflicts. Use terminate-tsql-conn to properly release connections. Issues Resolved BABEL-6457 Authored-by: Anju Bharti <abanju@amazon.com>
1 parent 9afe403 commit 1786c44

44 files changed

Lines changed: 727 additions & 7 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

contrib/babelfishpg_tds/src/backend/tds/tdslogin.c

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1162,6 +1162,46 @@ TdsResetLoginFlags()
11621162
* Returns STATUS_OK or STATUS_ERROR, or might call ereport(FATAL) and
11631163
* not return at all.
11641164
*/
1165+
1166+
/*
1167+
* tds_truncate_identifier_md5
1168+
*
1169+
* Truncate an identifier exceeding NAMEDATALEN using clip + MD5 hash,
1170+
* matching the behavior of pltsql_truncate_identifier for CI_AS collations.
1171+
* Used for pre-auth user name truncation (where the tsql hook is unavailable)
1172+
* and for database name truncation (for consistency with the stored form).
1173+
*/
1174+
#define MD5_HASH_LEN 32
1175+
1176+
static void
1177+
tds_truncate_identifier_md5(char *ident, int len)
1178+
{
1179+
char md5[MD5_HASH_LEN + 1];
1180+
char buf[NAMEDATALEN];
1181+
const char *errstr = NULL;
1182+
char *downcased;
1183+
1184+
Assert(len >= NAMEDATALEN);
1185+
1186+
downcased = downcase_identifier(ident, len, false, false);
1187+
1188+
if (!pg_md5_hash(downcased, strlen(downcased), md5, &errstr))
1189+
ereport(FATAL,
1190+
(errcode(ERRCODE_INTERNAL_ERROR),
1191+
errmsg("could not compute %s hash: %s", "MD5", errstr)));
1192+
1193+
len = pg_mbcliplen(ident, len, NAMEDATALEN - MD5_HASH_LEN - 1);
1194+
Assert(len + MD5_HASH_LEN < NAMEDATALEN);
1195+
memcpy(buf, ident, len);
1196+
memcpy(buf + len, md5, MD5_HASH_LEN);
1197+
buf[len + MD5_HASH_LEN] = '\0';
1198+
1199+
pfree(downcased);
1200+
memcpy(ident, buf, len + MD5_HASH_LEN + 1);
1201+
}
1202+
1203+
#undef MD5_HASH_LEN
1204+
11651205
static int
11661206
ProcessLoginInternal(Port *port)
11671207
{
@@ -1251,13 +1291,19 @@ ProcessLoginInternal(Port *port)
12511291
loginInfo = request;
12521292

12531293
/*
1254-
* Truncate given database and user names to length of a Postgres name.
1255-
* This avoids lookup failures when overlength names are given.
1294+
* Truncate user name to fit Postgres NAMEDATALEN using TSQL-aware
1295+
* truncation (clip + MD5 hash) so the result matches the role name
1296+
* stored in the pg catalog. We cannot use truncate_identifier_hook here
1297+
* because babelfishpg_tsql is not yet loaded at this stage.
1298+
* Database name truncation is handled similarly in TdsSetDbContext().
12561299
*/
1257-
if (strlen(port->database_name) >= NAMEDATALEN)
1258-
port->database_name[NAMEDATALEN - 1] = '\0';
12591300
if (strlen(port->user_name) >= NAMEDATALEN)
1260-
port->user_name[NAMEDATALEN - 1] = '\0';
1301+
{
1302+
tds_truncate_identifier_md5(port->user_name, strlen(port->user_name));
1303+
1304+
pfree(loginInfo->username);
1305+
loginInfo->username = pstrdup(port->user_name);
1306+
}
12611307

12621308
/*
12631309
* Done putting stuff in TopMemoryContext.
@@ -2043,6 +2089,9 @@ TdsSetDbContext()
20432089
* SQL injection.
20442090
*/
20452091
StartTransactionCommand();
2092+
if (strlen(loginInfo->database) >= NAMEDATALEN)
2093+
tds_truncate_identifier_md5(loginInfo->database,
2094+
strlen(loginInfo->database));
20462095
db_id = pltsql_plugin_handler_ptr->pltsql_get_database_oid(loginInfo->database);
20472096
CommitTransactionCommand();
20482097
MemoryContextSwitchTo(oldContext);
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
-- tsql
2+
3+
-- Cleanup: drop all logins and databases created for long identifier tests
4+
-- Drop long logins
5+
DROP LOGIN long_identifier_login_test_user_abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqr;
6+
GO
7+
8+
DROP LOGIN Mixed_Case_Login_Test_XyZaBcDeFgHiJkLmNoPqRsTuVwXyZaBcDeFgHiJkLmNoPqRsTuVwXyZaBcDeFgHiJkLmNoPqRsTuVwXyZaBcDeFgHiJkLmNoPqRsTuVwXy;
9+
GO
10+
11+
DROP LOGIN Lögïn_Tëst_Üsér_àáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿàáâãäåæçèéêëìíîïðñò;
12+
GO
13+
14+
-- Drop boundary login
15+
DROP LOGIN boundary_test_login_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa;
16+
GO
17+
18+
-- Drop case-insensitive test login
19+
DROP LOGIN Case_Insensitive_Long_Login_Test_AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZzAaBbCcDdEeFfGgHhIiJjKkLlMmNn;
20+
GO
21+
22+
-- Drop bracketed long logins
23+
DROP LOGIN [long_bracket_login_test_user_abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstu];
24+
GO
25+
26+
DROP LOGIN [Bracketed_Mixed_Case_Login_XyZaBcDeFgHiJkLmNoPqRsTuVwXyZaBcDeFgHiJkLmNoPqRsTuVwXyZaBcDeFgHiJkLmNoPqRsTuVwXyZaBcDeFgHiJkLmNoPqRsT];
27+
GO
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
-- tsql
2+
3+
-- Test TSQL-aware identifier truncation for login and database names
4+
-- that exceed Postgres NAMEDATALEN (64 chars). TSQL allows up to 128 chars.
5+
-- The truncation uses clip + MD5 hash to produce a unique name that fits
6+
-- within NAMEDATALEN while preserving uniqueness.
7+
-- Scenario 1: Long login with 128 lowercase chars
8+
CREATE LOGIN long_identifier_login_test_user_abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqr WITH PASSWORD = '123';
9+
GO
10+
11+
-- Scenario 2: Long login with 128 mixed-case chars (different name from Scenario 1)
12+
-- Verifies that MD5 hash is computed on the downcased version (case-insensitive)
13+
CREATE LOGIN Mixed_Case_Login_Test_XyZaBcDeFgHiJkLmNoPqRsTuVwXyZaBcDeFgHiJkLmNoPqRsTuVwXyZaBcDeFgHiJkLmNoPqRsTuVwXyZaBcDeFgHiJkLmNoPqRsTuVwXy WITH PASSWORD = '123';
14+
GO
15+
16+
-- Scenario 3: Long login with 128 multibyte chars
17+
-- Verifies that pg_mbcliplen handles multibyte characters correctly
18+
CREATE LOGIN Lögïn_Tëst_Üsér_àáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿàáâãäåæçèéêëìíîïðñò WITH PASSWORD = '123';
19+
GO
20+
21+
-- Scenario 4: Boundary - login with exactly 64 chars (minimum that triggers truncation)
22+
CREATE LOGIN boundary_test_login_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa WITH PASSWORD = '123';
23+
GO
24+
25+
-- Scenario 5: Case-insensitive authentication test
26+
-- Create with mixed case, will authenticate with different cases to verify
27+
-- that the MD5 hash computed during login matches regardless of input case
28+
CREATE LOGIN Case_Insensitive_Long_Login_Test_AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZzAaBbCcDdEeFfGgHhIiJjKkLlMmNn WITH PASSWORD = '123';
29+
GO
30+
31+
32+
33+
-- Scenario 6: Bracketed long login with 128 chars (should pass)
34+
CREATE LOGIN [long_bracket_login_test_user_abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstu] WITH PASSWORD = '123';
35+
GO
36+
37+
-- Scenario 7: Bracketed long login with mixed case (128 chars, should pass)
38+
CREATE LOGIN [Bracketed_Mixed_Case_Login_XyZaBcDeFgHiJkLmNoPqRsTuVwXyZaBcDeFgHiJkLmNoPqRsTuVwXyZaBcDeFgHiJkLmNoPqRsTuVwXyZaBcDeFgHiJkLmNoPqRsT] WITH PASSWORD = '123';
39+
GO

0 commit comments

Comments
 (0)