You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

CapabilitiesHandler.kt 5.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. package com.dmdirc.ktirc.events
  2. import com.dmdirc.ktirc.IrcClient
  3. import com.dmdirc.ktirc.messages.sendAuthenticationMessage
  4. import com.dmdirc.ktirc.messages.sendCapabilityEnd
  5. import com.dmdirc.ktirc.messages.sendCapabilityRequest
  6. import com.dmdirc.ktirc.model.CapabilitiesNegotiationState
  7. import com.dmdirc.ktirc.model.CapabilitiesState
  8. import com.dmdirc.ktirc.model.Capability
  9. import com.dmdirc.ktirc.sasl.fromBase64
  10. import com.dmdirc.ktirc.util.logger
  11. internal class CapabilitiesHandler : EventHandler {
  12. private val log by logger()
  13. override fun processEvent(client: IrcClient, event: IrcEvent): List<IrcEvent> {
  14. when (event) {
  15. is ServerCapabilitiesReceived -> handleCapabilitiesReceived(client.serverState.capabilities, event.capabilities)
  16. is ServerCapabilitiesFinished -> handleCapabilitiesFinished(client)
  17. is ServerCapabilitiesAcknowledged -> handleCapabilitiesAcknowledged(client, event.capabilities)
  18. is AuthenticationMessage -> handleAuthenticationMessage(client, event.argument)
  19. is SaslMechanismNotAvailableError -> handleSaslMechanismChange(client, event.mechanisms)
  20. is SaslFinished -> handleSaslFinished(client)
  21. }
  22. return emptyList()
  23. }
  24. private fun handleCapabilitiesReceived(state: CapabilitiesState, capabilities: Map<Capability, String>) {
  25. state.advertisedCapabilities.putAll(capabilities)
  26. }
  27. private fun handleCapabilitiesFinished(client: IrcClient) {
  28. // TODO: We probably need to split the outgoing REQ lines if there are lots of caps
  29. // TODO: For caps with values we may need to decide which value to use/whether to enable them/etc
  30. with(client.serverState.capabilities) {
  31. if (advertisedCapabilities.keys.isEmpty()) {
  32. negotiationState = CapabilitiesNegotiationState.FINISHED
  33. client.sendCapabilityEnd()
  34. } else {
  35. negotiationState = CapabilitiesNegotiationState.AWAITING_ACK
  36. advertisedCapabilities.keys.map { it.name }.let {
  37. log.info { "Requesting capabilities: ${it.toList()}" }
  38. client.sendCapabilityRequest(it)
  39. }
  40. }
  41. }
  42. }
  43. private fun handleCapabilitiesAcknowledged(client: IrcClient, capabilities: Map<Capability, String>) {
  44. // TODO: Check if everything we wanted is enabled
  45. with(client.serverState.capabilities) {
  46. log.info { "Acknowledged capabilities: ${capabilities.keys.map { it.name }.toList()}" }
  47. enabledCapabilities.putAll(capabilities)
  48. if (client.serverState.sasl.mechanisms.isNotEmpty()) {
  49. enabledCapabilities[Capability.SaslAuthentication]?.let { serverCaps ->
  50. if (startSaslAuth(client, serverCaps.split(','))) {
  51. return
  52. }
  53. }
  54. log.warning { "SASL is enabled but we couldn't negotiate a SASL mechanism with the server" }
  55. }
  56. client.endNegotiation()
  57. }
  58. }
  59. private fun handleSaslMechanismChange(client: IrcClient, mechanisms: Collection<String>) {
  60. if (!startSaslAuth(client, mechanisms)) {
  61. log.warning { "SASL is enabled but we couldn't negotiate a SASL mechanism with the server" }
  62. client.endNegotiation()
  63. }
  64. }
  65. private fun startSaslAuth(client: IrcClient, serverMechanisms: Collection<String>) =
  66. with(client.serverState) {
  67. sasl.getPreferredSaslMechanism(serverMechanisms)?.let { mechanism ->
  68. log.info { "Attempting SASL authentication using ${mechanism.ircName}" }
  69. sasl.currentMechanism = mechanism
  70. capabilities.negotiationState = CapabilitiesNegotiationState.AUTHENTICATING
  71. client.sendAuthenticationMessage(mechanism.ircName)
  72. true
  73. } ?: false
  74. }
  75. private fun handleAuthenticationMessage(client: IrcClient, argument: String?) {
  76. if (argument?.length == 400) {
  77. client.serverState.sasl.saslBuffer += argument
  78. return
  79. }
  80. client.serverState.sasl.currentMechanism?.let {
  81. it.handleAuthenticationEvent(client, client.getStoredSaslBuffer(argument)?.fromBase64())
  82. } ?: run {
  83. client.sendAuthenticationMessage("*")
  84. }
  85. }
  86. private fun handleSaslFinished(client: IrcClient) = with(client) {
  87. with(serverState.sasl) {
  88. saslBuffer = ""
  89. mechanismState = null
  90. currentMechanism = null
  91. }
  92. endNegotiation()
  93. }
  94. private fun IrcClient.endNegotiation() {
  95. serverState.capabilities.negotiationState = CapabilitiesNegotiationState.FINISHED
  96. sendCapabilityEnd()
  97. }
  98. private fun IrcClient.getStoredSaslBuffer(argument: String?): String? {
  99. val data = serverState.sasl.saslBuffer + (argument ?: "")
  100. serverState.sasl.saslBuffer = ""
  101. return if (data.isEmpty()) null else data
  102. }
  103. }