Skip to content

Commit 33b717b

Browse files
author
David Shiflet (from Dev Box)
committed
Add HostNameInCertificate
1 parent e8f9c26 commit 33b717b

5 files changed

Lines changed: 52 additions & 15 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ The following switches have different behavior in this version of `sqlcmd` compa
130130
- If `-N` is provided but `-C` is not, sqlcmd will require validation of the server certificate. Note that a `false` value for encryption could still lead to encryption of the login packet.
131131
- `-C` has no effect when `strict` value is specified for `-N`.
132132
- If both `-N` and `-C` are provided, sqlcmd will use their values for encryption negotiation.
133+
- To provide the value of the host name in the server certificate when using strict encryption, append the name after a `:` to the `-Ns[trict]`. Example: `-Ns:myhost.domain.com`
133134
- More information about client/server encryption negotiation can be found at <https://docs.microsoft.com/openspecs/windows_protocols/ms-tds/60f56408-0188-4cd5-8b90-25c6f2423868>
134135
- `-u` The generated Unicode output file will have the UTF16 Little-Endian Byte-order mark (BOM) written to it.
135136
- Some behaviors that were kept to maintain compatibility with `OSQL` may be changed, such as alignment of column headers for some data types.

cmd/sqlcmd/sqlcmd.go

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -314,14 +314,16 @@ func checkDefaultValue(args []string, i int) (val string) {
314314
'k': "0",
315315
'L': "|", // | is the sentinel for no value since users are unlikely to use it. It's "reserved" in most shells
316316
'X': "0",
317-
'N': "true",
318317
}
319318
if isFlag(args[i]) && (len(args) == i+1 || args[i+1][0] == '-') {
320319
if v, ok := flags[rune(args[i][1])]; ok {
321320
val = v
322321
return
323322
}
324323
}
324+
if args[i] == "-N" && (len(args) == i+1 || args[i+1][0] == '-') {
325+
val = "true"
326+
}
325327
return
326328
}
327329

@@ -406,7 +408,7 @@ func setFlags(rootCmd *cobra.Command, args *SQLCmdArguments) {
406408
rootCmd.Flags().StringVarP(&args.EncryptConnection, encryptConnection, "N", "default", localizer.Sprintf("This switch is used by the client to request an encrypted connection"))
407409
// Can't use NoOptDefVal until this fix: https://github.com/spf13/cobra/issues/866
408410
//rootCmd.Flags().Lookup(encryptConnection).NoOptDefVal = "true"
409-
rootCmd.Flags().StringVarP(&args.Format, format, "F", "horiz", localizer.Sprintf("Specifies the formatting for results"))
411+
rootCmd.Flags().StringVarP(&args.Format, format, "F", "horiz", localizer.Sprintf("Specifies the formatting for results. Can be either %s or %s. The default is %s.", "horiz[ontal]", "vert[ical]", "horiz[ontal]"))
410412
_ = rootCmd.Flags().IntP(errorsToStderr, "r", -1, localizer.Sprintf("%s Redirects error messages with severity >= 11 output to stderr. Pass 1 to to redirect all errors including PRINT.", "-r[0 | 1]"))
411413
rootCmd.Flags().IntVar(&args.DriverLoggingLevel, "driver-logging-level", 0, localizer.Sprintf("Level of mssql driver messages to print"))
412414
rootCmd.Flags().BoolVarP(&args.ExitOnError, "exit-on-error", "b", false, localizer.Sprintf("Specifies that sqlcmd exits and returns a %s value when an error occurs", localizer.DosErrorLevel))
@@ -463,11 +465,14 @@ func normalizeFlags(cmd *cobra.Command) error {
463465
}
464466
case encryptConnection:
465467
value := strings.ToLower(v)
468+
if strictEncryptRegexp.MatchString(value) {
469+
return pflag.NormalizedName(name)
470+
}
466471
switch value {
467472
case "mandatory", "yes", "1", "t", "true", "disable", "optional", "no", "0", "f", "false", "strict", "m", "s", "o":
468473
return pflag.NormalizedName(name)
469474
default:
470-
err = invalidParameterError("-N", v, "m[andatory]", "yes", "1", "t[rue]", "disable", "o[ptional]", "no", "0", "f[alse]", "s[trict]")
475+
err = invalidParameterError("-N", v, "m[andatory]", "yes", "1", "t[rue]", "disable", "o[ptional]", "no", "0", "f[alse]", "s[trict][:hostnameincertificate]")
471476
return pflag.NormalizedName("")
472477
}
473478
case format:
@@ -525,6 +530,7 @@ func normalizeFlags(cmd *cobra.Command) error {
525530
var invalidArgRegexp = regexp.MustCompile(`invalid argument \"(.*)\" for \"(-.), (--.*)\"`)
526531
var missingArgRegexp = regexp.MustCompile(`flag needs an argument: '.' in (-.)`)
527532
var unknownArgRegexp = regexp.MustCompile(`unknown shorthand flag: '(.)' in -.`)
533+
var strictEncryptRegexp = regexp.MustCompile(`^((s)|(strict)):(.+)`)
528534

529535
func rangeParameterError(flag string, value string, min int, max int, inclusive bool) error {
530536
if inclusive {
@@ -689,15 +695,21 @@ func setConnect(connect *sqlcmd.ConnectSettings, args *SQLCmdArguments, vars *sq
689695
connect.DisableVariableSubstitution = args.DisableVariableSubstitution
690696
connect.ApplicationIntent = args.ApplicationIntent
691697
connect.LoginTimeoutSeconds = args.LoginTimeout
692-
switch args.EncryptConnection {
693-
case "s":
698+
matches := strictEncryptRegexp.FindStringSubmatch(args.EncryptConnection)
699+
if len(matches) == 5 {
694700
connect.Encrypt = "strict"
695-
case "o":
696-
connect.Encrypt = "optional"
697-
case "m":
698-
connect.Encrypt = "mandatory"
699-
default:
700-
connect.Encrypt = args.EncryptConnection
701+
connect.HostNameInCertificate = matches[4]
702+
} else {
703+
switch args.EncryptConnection {
704+
case "s":
705+
connect.Encrypt = "strict"
706+
case "o":
707+
connect.Encrypt = "optional"
708+
case "m":
709+
connect.Encrypt = "mandatory"
710+
default:
711+
connect.Encrypt = args.EncryptConnection
712+
}
701713
}
702714
connect.PacketSize = args.PacketSize
703715
connect.WorkstationName = args.WorkstationName

cmd/sqlcmd/sqlcmd_test.go

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,9 @@ func TestValidCommandLineToArgsConversion(t *testing.T) {
108108
{[]string{"-ifoo.sql", "bar.sql", "-V10"}, func(args SQLCmdArguments) bool {
109109
return args.ErrorSeverityLevel == 10 && args.InputFile[0] == "foo.sql" && args.InputFile[1] == "bar.sql"
110110
}},
111+
{[]string{"-N", "s:myserver.domain.com"}, func(args SQLCmdArguments) bool {
112+
return args.EncryptConnection == "s:myserver.domain.com"
113+
}},
111114
}
112115

113116
for _, test := range commands {
@@ -580,25 +583,40 @@ func TestConvertOsArgs(t *testing.T) {
580583

581584
func TestEncryptionOptions(t *testing.T) {
582585
type test struct {
583-
input string
584-
output string
586+
input string
587+
output string
588+
hostnameincertificate string
585589
}
586590
tests := []test{
587591
{
588592
"s",
589593
"strict",
594+
"",
590595
},
591596
{
592597
"m",
593598
"mandatory",
599+
"",
594600
},
595601
{
596602
"o",
597603
"optional",
604+
"",
598605
},
599606
{
600607
"mandatory",
601608
"mandatory",
609+
"",
610+
},
611+
{
612+
"s:myserver.domain.com",
613+
"strict",
614+
"myserver.domain.com",
615+
},
616+
{
617+
"strict:myserver.domain.com",
618+
"strict",
619+
"myserver.domain.com",
602620
},
603621
}
604622
for _, c := range tests {
@@ -610,6 +628,7 @@ func TestEncryptionOptions(t *testing.T) {
610628
var connectConfig sqlcmd.ConnectSettings
611629
setConnect(&connectConfig, &args, vars)
612630
assert.Equal(t, c.output, connectConfig.Encrypt, "Incorrect connect.Encrypt")
631+
assert.Equal(t, c.hostnameincertificate, connectConfig.HostNameInCertificate, "Incorrect connect.HostNameInCertificate")
613632
})
614633
}
615634
}

pkg/sqlcmd/connect.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ type ConnectSettings struct {
5858
EnableColumnEncryption bool
5959
// ChangePassword is the new password for the user to set during login
6060
ChangePassword string
61+
// The HostNameInCertificate is the name to use for the host in the certificate validation
62+
HostNameInCertificate string
6163
}
6264

6365
func (c ConnectSettings) authenticationMethod() string {
@@ -145,6 +147,9 @@ func (connect ConnectSettings) ConnectionString() (connectionString string, err
145147
if connect.Encrypt != "" && connect.Encrypt != "default" {
146148
query.Add(msdsn.Encrypt, connect.Encrypt)
147149
}
150+
if connect.HostNameInCertificate != "" {
151+
query.Add(msdsn.HostNameInCertificate, connect.HostNameInCertificate)
152+
}
148153
if connect.LogLevel > 0 {
149154
query.Add(msdsn.LogParam, fmt.Sprint(connect.LogLevel))
150155
}

pkg/sqlcmd/sqlcmd_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ func TestConnectionStringFromSqlCmd(t *testing.T) {
5151
"sqlserver://someserver:1045?protocol=tcp&trustservercertificate=true",
5252
},
5353
{
54-
&ConnectSettings{ServerName: `tcp:someserver,1045`},
55-
"sqlserver://someserver:1045?protocol=tcp",
54+
&ConnectSettings{ServerName: `tcp:someserver,1045`, Encrypt: "strict", HostNameInCertificate: "*.mydomain.com"},
55+
"sqlserver://someserver:1045?encrypt=strict&hostnameincertificate=%2A.mydomain.com&protocol=tcp",
5656
},
5757
{
5858
&ConnectSettings{ServerName: "someserver", AuthenticationMethod: azuread.ActiveDirectoryServicePrincipal, UserName: "myapp@mytenant", Password: pwd},

0 commit comments

Comments
 (0)