package inbound import ( "context" "encoding/base64" "fmt" cmpp "github.com/bigwhite/gocmpp" "log" "net" "strings" "sync" "time" ) type authRequest struct { Account string `json:"account"` AuthSource string `json:"authSource"` Timestamp uint32 `json:"timestamp"` RemoteIP string `json:"remoteIp,omitempty"` } type authResponse struct { PasswordCipher string `json:"passwordCipher"` ApplicationID string `json:"applicationId"` TenantID string `json:"tenantId"` Account string `json:"account"` EnterpriseCode string `json:"enterpriseCode"` MaxConnections int `json:"maxConnections"` } func (s Server) handleLogin(response *cmpp.Response, packet *cmpp.Packet, logger *log.Logger) (bool, error) { req, ok := packet.Packer.(*cmpp.CmppConnReqPkt) if !ok { return true, nil } account := strings.TrimRight(req.SrcAddr, "\x00") if account == "" { setInboundConnectResponse(response.Packer, cmpp.ErrnoConnInvalidSrcAddr, req.AuthSrc, "", req.Version) return false, cmpp.ConnRspStatusErrMap[cmpp.ErrnoConnInvalidSrcAddr] } if req.Version != cmpp.V20 && req.Version != cmpp.V21 && req.Version != cmpp.V30 { setInboundConnectResponse(response.Packer, cmpp.ErrnoConnVerTooHigh, req.AuthSrc, "", cmpp.V30) return false, cmpp.ConnRspStatusErrMap[cmpp.ErrnoConnVerTooHigh] } auth, err := s.authenticate(packet.Conn.Conn.RemoteAddr(), account, req.AuthSrc, req.Timestamp) if err != nil { logger.Printf("cmpp inbound auth failed account=%s remote=%s err=%v", account, packet.Conn.Conn.RemoteAddr(), err) setInboundConnectResponse(response.Packer, cmpp.ErrnoConnAuthFailed, req.AuthSrc, "", req.Version) return false, cmpp.ConnRspStatusErrMap[cmpp.ErrnoConnAuthFailed] } now := time.Now().UTC() session := &downstreamSession{ account: strings.TrimSpace(defaultString(auth.Account, account)), tenantID: strings.TrimSpace(auth.TenantID), applicationID: strings.TrimSpace(auth.ApplicationID), enterpriseCode: strings.TrimSpace(auth.EnterpriseCode), protocol: cmppVersionName(req.Version), srcID: strings.TrimSpace(auth.Account), remoteIP: remoteIP(packet.Conn.Conn.RemoteAddr()), connectedAt: now, connectionID: fmt.Sprintf("%s-%d", s.gatewayInstanceID(), now.UnixNano()), conn: packet.Conn, mu: &sync.Mutex{}, presence: s.PresenceStore, instanceID: s.gatewayInstanceID(), report: s.reportConnection, deliveryReport: s.reportDownstreamDelivery, protocolLog: s.emitProtocolLog, } if !rememberAccount(session, auth.MaxConnections) { logger.Printf("cmpp inbound auth failed account=%s remote=%s err=connection limit exceeded max=%d", account, packet.Conn.Conn.RemoteAddr(), auth.MaxConnections) setInboundConnectResponse(response.Packer, cmpp.ErrnoConnOthers, req.AuthSrc, "", req.Version) return false, cmpp.ConnRspStatusErrMap[cmpp.ErrnoConnOthers] } setInboundConnectResponse(response.Packer, 0, req.AuthSrc, auth.PasswordCipher, req.Version) go s.reportConnectionOrDisconnect(session, "connected", "") response.AfterSend = func(sendErr error) { if sendErr == nil { go s.flushPending(defaultString(auth.Account, account), logger) } } logger.Printf( "cmpp inbound event=login_accepted protocol=%s requested_version=0x%02x response_version=0x%02x account=%s remote=%s", cmppVersionName(req.Version), uint8(req.Version), uint8(req.Version), account, packet.Conn.Conn.RemoteAddr(), ) return false, nil } func setInboundConnectResponse(packet any, status uint8, authSource string, secret string, version cmpp.Type) { switch resp := packet.(type) { case *cmpp.Cmpp2ConnRspPkt: resp.Status = status resp.AuthSrc = authSource resp.Secret = secret resp.Version = version case *cmpp.Cmpp3ConnRspPkt: resp.Status = uint32(status) resp.AuthSrc = authSource resp.Secret = secret resp.Version = version } } func cmppVersionName(version cmpp.Type) string { switch version { case cmpp.V20: return "cmpp20" case cmpp.V21: return "cmpp21" case cmpp.V30: return "cmpp30" default: return fmt.Sprintf("unknown_0x%02x", uint8(version)) } } func (s Server) authenticate(remote net.Addr, account string, authSource string, timestamp uint32) (authResponse, error) { payload := authRequest{ Account: account, AuthSource: base64.StdEncoding.EncodeToString([]byte(authSource)), Timestamp: timestamp, RemoteIP: remoteIP(remote), } var result authResponse err := s.post(context.Background(), "/gateway/events/inbound/authenticate", payload, &result) return result, err }