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.2KB

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