{"record":{"id":"f5273f60e2dc7ad7","repo":"shadow1ng/fscan","slug":"mcs-disconnect-provider-ultimatum","errorCode":null,"errorMessage":"MCS DISCONNECT_PROVIDER_ULTIMATUM","messagePattern":"MCS DISCONNECT_PROVIDER_ULTIMATUM","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"libs/grdp/protocol/t125/mcs.go","lineNumber":450,"sourceCode":"\tbuff := &bytes.Buffer{}\n\twriteMCSPDUHeader(CHANNEL_JOIN_REQUEST, 0, buff)\n\tper.WriteInteger16(c.userId-MCS_USERCHANNEL_BASE, buff)\n\tper.WriteInteger16(channelId, buff)\n\tc.transport.Write(buff.Bytes())\n}\n\nfunc (c *MCSClient) recvData(s []byte) {\n\tglog.Debug(\"msc on data recvData:\", hex.EncodeToString(s))\n\n\tr := bytes.NewReader(s)\n\toption, err := core.ReadUInt8(r)\n\tif err != nil {\n\t\tc.Emit(\"error\", err)\n\t\treturn\n\t}\n\n\tif readMCSPDUHeader(option, DISCONNECT_PROVIDER_ULTIMATUM) {\n\t\tc.Emit(\"error\", errors.New(\"MCS DISCONNECT_PROVIDER_ULTIMATUM\"))\n\t\tc.transport.Close()\n\t\treturn\n\t} else if !readMCSPDUHeader(option, c.recvOpCode) {\n\t\tc.Emit(\"error\", errors.New(\"Invalid expected MCS opcode receive data\"))\n\t\treturn\n\t}\n\n\tuserId, _ := per.ReadInteger16(r)\n\tuserId += MCS_USERCHANNEL_BASE\n\n\tchannelId, _ := per.ReadInteger16(r)\n\tper.ReadEnumerates(r)\n\tsize, _ := per.ReadLength(r)\n\t// channel ID doesn't match a requested layer\n\tfound := false\n\tchannelName := \"\"\n\tfor _, channel := range c.channels {\n\t\tif channel.ID == channelId {","sourceCodeStart":432,"sourceCodeEnd":468,"githubUrl":"https://github.com/shadow1ng/fscan/blob/95cc12e753bf43de7004e5aef42a9ffba3934303/libs/grdp/protocol/t125/mcs.go#L432-L468","documentation":"In recvData, the steady-state MCS data handler, the first check tests whether the incoming PDU is a DISCONNECT_PROVIDER_ULTIMATUM (opcode 8). If so, the server has torn down the MCS domain/connection, and this error is emitted before closing the transport. It means the server unilaterally disconnected the session.","triggerScenarios":"Any 'data' event handled by recvData whose first byte decodes (option>>2) to DISCONNECT_PROVIDER_ULTIMATUM — the server sends this PDU instead of a SEND_DATA_INDICATION.","commonSituations":"Idle session disconnected by server policy/timeout; administrator logs off or disconnects the session; server crashes or restarts; license expiry; network device (NAT/firewall) idle-timeout prompting server teardown.","solutions":["Treat it as a server-initiated disconnect: close client resources and reconnect if the session should persist.","Implement keep-alive/application-level activity to prevent idle-timeout disconnects.","Check server event logs for the disconnect reason around the time of failure.","Add automatic reconnection logic in the consumer of the 'error' event."],"exampleFix":"// before\nc.Emit(\"error\", errors.New(\"MCS DISCONNECT_PROVIDER_ULTIMATUM\"))\nc.transport.Close()\nreturn\n// after\nc.Emit(\"error\", errors.New(\"MCS DISCONNECT_PROVIDER_ULTIMATUM: server disconnected the session\"))\nc.transport.Close()\nc.scheduleReconnect() // consumer-side auto-reconnect","handlingStrategy":"try-catch","validationCode":"// Detect server disconnect PDU early in the data stream:\nif (buf[0]>>2) == 8 { // DISCONNECT_PROVIDER_ULTIMATUM\n    // server is tearing the session down\n}","typeGuard":null,"tryCatchPattern":"mcs.On(\"error\", func(err error) {\n    if strings.Contains(err.Error(), \"DISCONNECT_PROVIDER_ULTIMATUM\") {\n        // clean up, notify user, and trigger reconnect flow\n    }\n})","preventionTips":["Keep the session active to avoid idle-timeout disconnects.","Monitor server event logs for admin-initiated or policy disconnects.","Implement automatic reconnection in the client consuming the 'error' event."],"tags":["rdp","mcs","disconnect","server-initiated"],"backgroundTag":"server-disconnected-session","analyzedSha":"95cc12e753bf43de7004e5aef42a9ffba3934303","analyzedAt":"2026-09-06T17:07:30.094Z","contentChangedAt":"2026-09-06T17:07:30.094Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}