diff --git a/server/errors.go b/server/errors.go index 8efa7ac02ea..62c054cdbee 100644 --- a/server/errors.go +++ b/server/errors.go @@ -153,6 +153,9 @@ var ( // Gateway's name. ErrWrongGateway = errors.New("wrong gateway") + // ErrGatewayNameHasSpaces signals that the gateway name contains spaces, which is not allowed. + ErrGatewayNameHasSpaces = errors.New("gateway name cannot contain spaces") + // ErrNoSysAccount is returned when an attempt to publish or subscribe is made // when there is no internal system account defined. ErrNoSysAccount = errors.New("system account not setup") @@ -163,6 +166,9 @@ var ( // ErrServerNotRunning is used to signal an error that a server is not running. ErrServerNotRunning = errors.New("server is not running") + // ErrServerNameHasSpaces signals that the server name contains spaces, which is not allowed. + ErrServerNameHasSpaces = errors.New("server name cannot contain spaces") + // ErrBadMsgHeader signals the parser detected a bad message header ErrBadMsgHeader = errors.New("bad message header detected") @@ -180,6 +186,9 @@ var ( // ErrClusterNameRemoteConflict signals that a remote server has a different cluster name. ErrClusterNameRemoteConflict = errors.New("cluster name from remote server conflicts") + // ErrClusterNameHasSpaces signals that the cluster name contains spaces, which is not allowed. + ErrClusterNameHasSpaces = errors.New("cluster name cannot contain spaces") + // ErrMalformedSubject is returned when a subscription is made with a subject that does not conform to subject rules. ErrMalformedSubject = errors.New("malformed subject") diff --git a/server/events_test.go b/server/events_test.go index 0d52ca20804..f27fe86b618 100644 --- a/server/events_test.go +++ b/server/events_test.go @@ -103,14 +103,14 @@ func runTrustedCluster(t *testing.T) (*Server, *Options, *Server, *Options, nkey mr.Store(apub, jwt) optsA := DefaultOptions() - optsA.Cluster.Name = "TEST CLUSTER 22" + optsA.Cluster.Name = "TEST_CLUSTER_22" optsA.Cluster.Host = "127.0.0.1" optsA.TrustedKeys = []string{pub} optsA.AccountResolver = mr optsA.SystemAccount = apub optsA.ServerName = "A_SRV" // Add in dummy gateway - optsA.Gateway.Name = "TEST CLUSTER 22" + optsA.Gateway.Name = "TEST_CLUSTER_22" optsA.Gateway.Host = "127.0.0.1" optsA.Gateway.Port = -1 optsA.gatewaysSolicitDelay = 30 * time.Second @@ -1876,7 +1876,7 @@ func TestServerEventsStatsZ(t *testing.T) { if m.Server.ID != sa.ID() { t.Fatalf("Did not match IDs") } - if m.Server.Cluster != "TEST CLUSTER 22" { + if m.Server.Cluster != "TEST_CLUSTER_22" { t.Fatalf("Did not match cluster name") } if m.Server.Version != VERSION { @@ -2978,11 +2978,11 @@ func TestServerEventsPingMonitorz(t *testing.T) { []string{"now", "routes"}}, {"ROUTEZ", json.RawMessage(`{"name":""}`), &Routez{}, []string{"now", "routes"}}, - {"ROUTEZ", json.RawMessage(`{"cluster":"TEST CLUSTER 22"}`), &Routez{}, + {"ROUTEZ", json.RawMessage(`{"cluster":"TEST_CLUSTER_22"}`), &Routez{}, []string{"now", "routes"}}, {"ROUTEZ", json.RawMessage(`{"cluster":"CLUSTER"}`), &Routez{}, []string{"now", "routes"}}, - {"ROUTEZ", json.RawMessage(`{"cluster":"TEST CLUSTER 22", "subscriptions":true}`), &Routez{}, + {"ROUTEZ", json.RawMessage(`{"cluster":"TEST_CLUSTER_22", "subscriptions":true}`), &Routez{}, []string{"now", "routes"}}, {"JSZ", nil, &JSzOptions{}, []string{"now", "disabled"}}, @@ -3083,8 +3083,8 @@ func TestGatewayNameClientInfo(t *testing.T) { if err != nil { t.Fatalf("Could not parse INFO json: %v\n", err) } - if info.Cluster != "TEST CLUSTER 22" { - t.Fatalf("Expected a cluster name of 'TEST CLUSTER 22', got %q", info.Cluster) + if info.Cluster != "TEST_CLUSTER_22" { + t.Fatalf("Expected a cluster name of 'TEST_CLUSTER_22', got %q", info.Cluster) } } diff --git a/server/gateway.go b/server/gateway.go index d8d434a26e4..be175467b38 100644 --- a/server/gateway.go +++ b/server/gateway.go @@ -18,12 +18,14 @@ import ( "crypto/sha256" "crypto/tls" "encoding/json" + "errors" "fmt" "math/rand" "net" "net/url" "sort" "strconv" + "strings" "sync" "sync/atomic" "time" @@ -299,17 +301,20 @@ func (r *RemoteGatewayOpts) clone() *RemoteGatewayOpts { // Ensure that gateway is properly configured. func validateGatewayOptions(o *Options) error { - if o.Gateway.Name == "" && o.Gateway.Port == 0 { + if o.Gateway.Name == _EMPTY_ && o.Gateway.Port == 0 { return nil } - if o.Gateway.Name == "" { - return fmt.Errorf("gateway has no name") + if o.Gateway.Name == _EMPTY_ { + return errors.New("gateway has no name") + } + if strings.Contains(o.Gateway.Name, " ") { + return ErrGatewayNameHasSpaces } if o.Gateway.Port == 0 { return fmt.Errorf("gateway %q has no port specified (select -1 for random port)", o.Gateway.Name) } for i, g := range o.Gateway.Gateways { - if g.Name == "" { + if g.Name == _EMPTY_ { return fmt.Errorf("gateway in the list %d has no name", i) } if len(g.URLs) == 0 { diff --git a/server/leafnode.go b/server/leafnode.go index f8624406fdd..61ba2207d5e 100644 --- a/server/leafnode.go +++ b/server/leafnode.go @@ -1288,6 +1288,13 @@ func (c *client) processLeafnodeInfo(info *Info) { c.closeConnection(WrongPort) return } + // Reject a cluster that contains spaces. + if info.Cluster != _EMPTY_ && strings.Contains(info.Cluster, " ") { + c.mu.Unlock() + c.sendErrAndErr(ErrClusterNameHasSpaces.Error()) + c.closeConnection(ProtocolViolation) + return + } // Capture a nonce here. c.nonce = []byte(info.Nonce) if info.TLSRequired && didSolicit { @@ -1779,6 +1786,13 @@ func (c *client) processLeafNodeConnect(s *Server, arg []byte, lang string) erro return err } + // Reject a cluster that contains spaces. + if proto.Cluster != _EMPTY_ && strings.Contains(proto.Cluster, " ") { + c.sendErrAndErr(ErrClusterNameHasSpaces.Error()) + c.closeConnection(ProtocolViolation) + return ErrClusterNameHasSpaces + } + // Check for cluster name collisions. if cn := s.cachedClusterName(); cn != _EMPTY_ && proto.Cluster != _EMPTY_ && proto.Cluster == cn { c.sendErrAndErr(ErrLeafNodeHasSameClusterName.Error()) diff --git a/server/opts.go b/server/opts.go index e717f26ea43..08212f431f9 100644 --- a/server/opts.go +++ b/server/opts.go @@ -917,7 +917,13 @@ func (o *Options) processConfigFileLine(k string, v any, errors *[]error, warnin case "port": o.Port = int(v.(int64)) case "server_name": - o.ServerName = v.(string) + sn := v.(string) + if strings.Contains(sn, " ") { + err := &configErr{tk, ErrServerNameHasSpaces.Error()} + *errors = append(*errors, err) + return + } + o.ServerName = sn case "host", "net": o.Host = v.(string) case "debug": @@ -1690,7 +1696,13 @@ func parseCluster(v any, opts *Options, errors *[]error, warnings *[]error) erro tk, mv = unwrapValue(mv, <) switch strings.ToLower(mk) { case "name": - opts.Cluster.Name = mv.(string) + cn := mv.(string) + if strings.Contains(cn, " ") { + err := &configErr{tk, ErrClusterNameHasSpaces.Error()} + *errors = append(*errors, err) + continue + } + opts.Cluster.Name = cn case "listen": hp, err := parseListen(mv) if err != nil { @@ -1920,7 +1932,13 @@ func parseGateway(v any, o *Options, errors *[]error, warnings *[]error) error { tk, mv = unwrapValue(mv, <) switch strings.ToLower(mk) { case "name": - o.Gateway.Name = mv.(string) + gn := mv.(string) + if strings.Contains(gn, " ") { + err := &configErr{tk, ErrGatewayNameHasSpaces.Error()} + *errors = append(*errors, err) + continue + } + o.Gateway.Name = gn case "listen": hp, err := parseListen(mv) if err != nil { diff --git a/server/server.go b/server/server.go index be21987bd9e..0479f319cee 100644 --- a/server/server.go +++ b/server/server.go @@ -1004,6 +1004,9 @@ func (s *Server) ClientURL() string { } func validateCluster(o *Options) error { + if o.Cluster.Name != _EMPTY_ && strings.Contains(o.Cluster.Name, " ") { + return ErrClusterNameHasSpaces + } if o.Cluster.Compression.Mode != _EMPTY_ { if err := validateAndNormalizeCompressionOption(&o.Cluster.Compression, CompressionS2Fast); err != nil { return err @@ -1013,8 +1016,9 @@ func validateCluster(o *Options) error { return fmt.Errorf("cluster: %v", err) } // Check that cluster name if defined matches any gateway name. - if o.Gateway.Name != "" && o.Gateway.Name != o.Cluster.Name { - if o.Cluster.Name != "" { + // Note that we have already verified that the gateway name does not have spaces. + if o.Gateway.Name != _EMPTY_ && o.Gateway.Name != o.Cluster.Name { + if o.Cluster.Name != _EMPTY_ { return ErrClusterNameConfigConflict } // Set this here so we do not consider it dynamic. @@ -1055,6 +1059,9 @@ func validateOptions(o *Options) error { return fmt.Errorf("max_payload (%v) cannot be higher than max_pending (%v)", o.MaxPayload, o.MaxPending) } + if o.ServerName != _EMPTY_ && strings.Contains(o.ServerName, " ") { + return errors.New("server name cannot contain spaces") + } // Check that the trust configuration is correct. if err := validateTrustedOperators(o); err != nil { return err diff --git a/server/server_test.go b/server/server_test.go index 08789a200a3..45ea9f93557 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -2150,3 +2150,53 @@ func TestServerConfigLastLineComments(t *testing.T) { require_NoError(t, err) defer nc.Close() } + +func TestServerClusterAndGatewayNameNoSpace(t *testing.T) { + conf := createConfFile(t, []byte(` + port: -1 + server_name: "my server" + `)) + _, err := ProcessConfigFile(conf) + require_Error(t, err, ErrServerNameHasSpaces) + + o := DefaultOptions() + o.ServerName = "my server" + _, err = NewServer(o) + require_Error(t, err, ErrServerNameHasSpaces) + + conf = createConfFile(t, []byte(` + port: -1 + server_name: "myserver" + cluster { + port: -1 + name: "my cluster" + } + `)) + _, err = ProcessConfigFile(conf) + require_Error(t, err, ErrClusterNameHasSpaces) + + o = DefaultOptions() + o.Cluster.Name = "my cluster" + o.Cluster.Port = -1 + _, err = NewServer(o) + require_Error(t, err, ErrClusterNameHasSpaces) + + conf = createConfFile(t, []byte(` + port: -1 + server_name: "myserver" + gateway { + port: -1 + name: "my gateway" + } + `)) + _, err = ProcessConfigFile(conf) + require_Error(t, err, ErrGatewayNameHasSpaces) + + o = DefaultOptions() + o.Cluster.Name = _EMPTY_ + o.Cluster.Port = 0 + o.Gateway.Name = "my gateway" + o.Gateway.Port = -1 + _, err = NewServer(o) + require_Error(t, err, ErrGatewayNameHasSpaces) +} diff --git a/test/leafnode_test.go b/test/leafnode_test.go index b1bfb132bb1..fb3dff89e8c 100644 --- a/test/leafnode_test.go +++ b/test/leafnode_test.go @@ -4426,3 +4426,27 @@ func TestLeafnodeHeaders(t *testing.T) { t.Fatalf("leaf msg header is empty") } } + +func TestLeafNodeClusterNameWithSpacesRejected(t *testing.T) { + s, opts := runLeafServer() + defer s.Shutdown() + + lc := createLeafConn(t, opts.LeafNode.Host, opts.LeafNode.Port) + defer lc.Close() + + checkInfoMsg(t, lc) + sendProto(t, lc, "CONNECT {\"cluster\":\"my cluster\"}\r\n") + expect := expectCommand(t, lc) + expect(errRe) + expectDisconnect(t, lc) + + lc = createLeafConn(t, opts.LeafNode.Host, opts.LeafNode.Port) + defer lc.Close() + leafSend, leafExpect := setupLeaf(t, lc, 1) + // The accept side does expect an INFO from an handshake, + // so it will "ignore" the first one. + leafSend("INFO {\"server\":\"server\"}\r\n") + leafSend("INFO {\"cluster\":\"my cluster\"}\r\n") + leafExpect(errRe) + expectDisconnect(t, lc) +}