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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. package com.dmdirc.ktirc.events.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.model.capabilities
  11. import com.dmdirc.ktirc.sasl.fromBase64
  12. import com.dmdirc.ktirc.util.logger
  13. internal class CapabilitiesHandler : EventHandler {
  14. private val log by logger()
  15. override fun processEvent(client: IrcClient, event: IrcEvent) {
  16. when (event) {
  17. is ServerCapabilitiesReceived -> handleCapabilitiesReceived(client.serverState.capabilities, event.capabilities)
  18. is ServerCapabilitiesFinished -> handleCapabilitiesFinished(client)
  19. is ServerCapabilitiesAcknowledged -> handleCapabilitiesAcknowledged(client, event.capabilities)
  20. is AuthenticationMessage -> handleAuthenticationMessage(client, event.argument)
  21. is SaslMechanismNotAvailableError -> handleSaslMechanismChange(client, event.mechanisms)
  22. is SaslFinished -> handleSaslFinished(client)
  23. }
  24. }
  25. private fun handleCapabilitiesReceived(state: CapabilitiesState, capabilities: Map<String, 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.let {
  38. log.info { "Requesting capabilities: ${it.toList()}" }
  39. client.sendCapabilityRequest(it)
  40. }
  41. }
  42. }
  43. }
  44. private fun handleCapabilitiesAcknowledged(client: IrcClient, ackedCapabilities: Map<String, String>) {
  45. // TODO: Check if everything we wanted is enabled
  46. with(client.serverState.capabilities) {
  47. log.info { "Acknowledged capabilities: ${ackedCapabilities.keys.toList()}" }
  48. ackedCapabilities.forEach { n, v ->
  49. capabilities[n]?.let {
  50. enabledCapabilities[it] = v
  51. }
  52. }
  53. if (client.serverState.sasl.mechanisms.isNotEmpty()) {
  54. // TODO: Icky. What if SASL had multiple names?
  55. advertisedCapabilities[Capability.SaslAuthentication.names[0]]?.let { serverCaps ->
  56. if (startSaslAuth(client, if (serverCaps.isEmpty()) emptyList() else serverCaps.split(','))) {
  57. return
  58. }
  59. }
  60. log.warning { "SASL is enabled but we couldn't negotiate a SASL mechanism with the server" }
  61. }
  62. client.endNegotiation()
  63. }
  64. }
  65. private fun handleSaslMechanismChange(client: IrcClient, mechanisms: Collection<String>) {
  66. if (!startSaslAuth(client, mechanisms)) {
  67. log.warning { "SASL is enabled but we couldn't negotiate a SASL mechanism with the server" }
  68. client.endNegotiation()
  69. }
  70. }
  71. private fun startSaslAuth(client: IrcClient, serverMechanisms: Collection<String>) =
  72. with(client.serverState) {
  73. sasl.getPreferredSaslMechanism(serverMechanisms)?.let { mechanism ->
  74. log.info { "Attempting SASL authentication using ${mechanism.ircName}" }
  75. sasl.currentMechanism = mechanism
  76. capabilities.negotiationState = CapabilitiesNegotiationState.AUTHENTICATING
  77. client.sendAuthenticationMessage(mechanism.ircName)
  78. true
  79. } ?: false
  80. }
  81. private fun handleAuthenticationMessage(client: IrcClient, argument: String?) {
  82. if (argument?.length == 400) {
  83. client.serverState.sasl.saslBuffer += argument
  84. return
  85. }
  86. client.serverState.sasl.currentMechanism?.let {
  87. it.handleAuthenticationEvent(client, client.getStoredSaslBuffer(argument)?.fromBase64())
  88. } ?: run {
  89. client.sendAuthenticationMessage("*")
  90. }
  91. }
  92. private fun handleSaslFinished(client: IrcClient) = with(client) {
  93. with(serverState.sasl) {
  94. saslBuffer = ""
  95. mechanismState = null
  96. currentMechanism = null
  97. }
  98. endNegotiation()
  99. }
  100. private fun IrcClient.endNegotiation() {
  101. serverState.capabilities.negotiationState = CapabilitiesNegotiationState.FINISHED
  102. sendCapabilityEnd()
  103. }
  104. private fun IrcClient.getStoredSaslBuffer(argument: String?): String? {
  105. val data = serverState.sasl.saslBuffer + (argument ?: "")
  106. serverState.sasl.saslBuffer = ""
  107. return if (data.isEmpty()) null else data
  108. }
  109. }