| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | package dbdriver |
| 4 | |
| 5 | import ( |
| 6 | "fmt" |
| 7 | "strings" |
| 8 | ) |
| 9 | |
| 10 | // SanitizeDSN masks sensitive information in DSN for logging |
| 11 | func SanitizeDSN(dsn string) string { |
| 12 | if dsn == "" { |
| 13 | return "<empty>" |
| 14 | } |
| 15 | |
| 16 | masked := dsn |
| 17 | |
| 18 | // Mask various password formats |
| 19 | passwordKeys := []string{"PWD=", "pwd=", "Pwd=", "PASSWORD=", "password=", "Password="} |
| 20 | for _, key := range passwordKeys { |
| 21 | if idx := strings.Index(masked, key); idx != -1 { |
| 22 | start := idx + len(key) |
| 23 | end := strings.IndexAny(masked[start:], ";") |
| 24 | if end == -1 { |
| 25 | masked = masked[:start] + "***" |
| 26 | } else { |
| 27 | masked = masked[:start] + "***" + masked[start+end:] |
| 28 | } |
| 29 | } |
| 30 | } |
| 31 | |
| 32 | // Mask authentication fields |
| 33 | authKeys := []string{"AUTHENTICATION=", "Authentication=", "authentication="} |
| 34 | for _, key := range authKeys { |
| 35 | if idx := strings.Index(masked, key); idx != -1 { |
| 36 | start := idx + len(key) |
| 37 | end := strings.IndexAny(masked[start:], ";") |
| 38 | if end == -1 { |
| 39 | masked = masked[:start] + "***" |
| 40 | } else { |
| 41 | masked = masked[:start] + "***" + masked[start+end:] |
| 42 | } |
| 43 | } |
| 44 | } |
| 45 | |
| 46 | return masked |
| 47 | } |
| 48 | |
| 49 | // containsDB2Keywords checks if DSN contains DB2-specific keywords |
| 50 | func containsDB2Keywords(dsn string) bool { |
| 51 | upperDSN := strings.ToUpper(dsn) |
| 52 | db2Keywords := []string{ |
| 53 | "DATABASE=", |
| 54 | "HOSTNAME=", |
| 55 | "PROTOCOL=TCPIP", |
| 56 | "UID=", |
| 57 | "PWD=", |
| 58 | "PORT=", |
| 59 | } |
| 60 | |
| 61 | matchCount := 0 |
| 62 | for _, keyword := range db2Keywords { |
| 63 | if strings.Contains(upperDSN, keyword) { |
| 64 | matchCount++ |
| 65 | } |
| 66 | } |
| 67 | |
| 68 | // If we have at least 3 DB2 keywords, it's likely a DB2 DSN |
| 69 | return matchCount >= 3 |
| 70 | } |
| 71 | |
| 72 | // containsODBCKeywords checks if DSN contains ODBC-specific keywords |
| 73 | func containsODBCKeywords(dsn string) bool { |
| 74 | upperDSN := strings.ToUpper(dsn) |
| 75 | odbcKeywords := []string{ |
| 76 | "DRIVER=", |
| 77 | "DSN=", |
| 78 | "DRIVER={", |
| 79 | "SYSTEM=", // AS/400 ODBC style |
| 80 | } |
| 81 | |
| 82 | for _, keyword := range odbcKeywords { |
| 83 | if strings.Contains(upperDSN, keyword) { |
| 84 | return true |
| 85 | } |
| 86 | } |
| 87 | |
| 88 | return false |
| 89 | } |
| 90 | |
| 91 | // EnsureDriver prepends the provided default driver if the DSN string does not specify one. |
| 92 | func EnsureDriver(dsn, defaultDriver string) string { |
| 93 | trimmed := strings.TrimSpace(dsn) |
| 94 | if trimmed == "" { |
| 95 | return dsn |
| 96 | } |
| 97 | |
| 98 | upper := strings.ToUpper(trimmed) |
| 99 | if strings.Contains(upper, "DRIVER=") { |
| 100 | return dsn |
| 101 | } |
| 102 | |
| 103 | if defaultDriver == "" { |
| 104 | defaultDriver = "IBM DB2 ODBC DRIVER" |
| 105 | } |
| 106 | |
| 107 | // Avoid duplicate separators when original DSN already begins with ';' |
| 108 | trimmed = strings.TrimLeft(trimmed, ";") |
| 109 | if trimmed != "" { |
| 110 | return fmt.Sprintf("Driver={%s};%s", defaultDriver, trimmed) |
| 111 | } |
| 112 | |
| 113 | return fmt.Sprintf("Driver={%s};", defaultDriver) |
| 114 | } |