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 4.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  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 SaslFinished -> handleSaslFinished(client)
  20. }
  21. return emptyList()
  22. }
  23. private fun handleCapabilitiesReceived(state: CapabilitiesState, capabilities: Map<Capability, String>) {
  24. state.advertisedCapabilities.putAll(capabilities)
  25. }
  26. private fun handleCapabilitiesFinished(client: IrcClient) {
  27. // TODO: We probably need to split the outgoing REQ lines if there are lots of caps
  28. // TODO: For caps with values we may need to decide which value to use/whether to enable them/etc
  29. with (client.serverState.capabilities) {
  30. if (advertisedCapabilities.keys.isEmpty()) {
  31. negotiationState = CapabilitiesNegotiationState.FINISHED
  32. client.sendCapabilityEnd()
  33. } else {
  34. negotiationState = CapabilitiesNegotiationState.AWAITING_ACK
  35. advertisedCapabilities.keys.map { it.name }.let {
  36. log.info { "Requesting capabilities: ${it.toList()}" }
  37. client.sendCapabilityRequest(it)
  38. }
  39. }
  40. }
  41. }
  42. private fun handleCapabilitiesAcknowledged(client: IrcClient, capabilities: Map<Capability, String>) {
  43. // TODO: Check if everything we wanted is enabled
  44. with (client.serverState.capabilities) {
  45. log.info { "Acknowledged capabilities: ${capabilities.keys.map { it.name }.toList()}" }
  46. enabledCapabilities.putAll(capabilities)
  47. if (client.hasCredentials) {
  48. client.serverState.sasl.getPreferredSaslMechanism(enabledCapabilities[Capability.SaslAuthentication])?.let { mechanism ->
  49. log.info { "Attempting SASL authentication using ${mechanism.ircName}" }
  50. client.serverState.sasl.currentMechanism = mechanism
  51. negotiationState = CapabilitiesNegotiationState.AUTHENTICATING
  52. client.sendAuthenticationMessage(mechanism.ircName)
  53. return
  54. }
  55. log.warning { "User supplied credentials but we couldn't negotiate a SASL mechanism with the server" }
  56. }
  57. client.endNegotiation()
  58. }
  59. }
  60. private fun handleAuthenticationMessage(client: IrcClient, argument: String?) {
  61. if (argument?.length == 400) {
  62. client.serverState.sasl.saslBuffer += argument
  63. return
  64. }
  65. client.serverState.sasl.currentMechanism?.let {
  66. it.handleAuthenticationEvent(client, client.getStoredSaslBuffer(argument)?.fromBase64())
  67. } ?: run {
  68. client.sendAuthenticationMessage("*")
  69. }
  70. }
  71. private fun handleSaslFinished(client: IrcClient) = with (client) {
  72. with (serverState.sasl) {
  73. saslBuffer = ""
  74. mechanismState = null
  75. currentMechanism = null
  76. }
  77. endNegotiation()
  78. }
  79. private fun IrcClient.endNegotiation() {
  80. serverState.capabilities.negotiationState = CapabilitiesNegotiationState.FINISHED
  81. sendCapabilityEnd()
  82. }
  83. private fun IrcClient.getStoredSaslBuffer(argument: String?): String? {
  84. val data = serverState.sasl.saslBuffer + (argument ?: "")
  85. serverState.sasl.saslBuffer = ""
  86. return if (data.isEmpty()) null else data
  87. }
  88. private val IrcClient.hasCredentials
  89. get() = profile.authUsername != null && profile.authPassword != null
  90. }