Repository: sopel-irc/sopel

Issue:
AUTHENTICATE arguments are not split.
When using a very long password, the authstring given with AUTHENTICATE exceeds 400 characters, and thus should be splitted, according to [the specification](http://ircv3.net/specs/extensions/sasl-3.1.html#the-authenticate-command)

Config file:

```
[core]
nick = Sopel
host = 0.0.0.0
use_ssl = false
port = 34827
owner = me
channels = 
auth_username = foo
auth_password = barbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbar
auth_method = sasl
```

Sopel session log:

```
C: CAP LS 302
S: CAP * LS :sasl
C: NICK Sopel
C: USER sopel +iw Sopel :Sopel: http://sopel.chat
C: CAP REQ multi-prefix
S: CAP Sopel NAK :multi-prefix
C: CAP REQ sasl
S: CAP Sopel ACK :sasl
C: AUTHENTICATE PLAIN
S: AUTHENTICATE +
C: AUTHENTICATE Zm9vAGZvbwBiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiY
```

Expected log:

```
C: CAP LS 302
S: CAP * LS :sasl
C: NICK Sopel
C: USER sopel +iw Sopel :Sopel: http://sopel.chat
C: CAP REQ multi-prefix
S: CAP Sopel NAK :multi-prefix
C: CAP REQ sasl
S: CAP Sopel ACK :sasl
C: AUTHENTICATE PLAIN
S: AUTHENTICATE +
C: AUTHENTICATE :Zm9vAGZvbwBiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJi
C: AUTHENTICATE :YXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJi
C: AUTHENTICATE :YXJiYXJiYXI=
```

Version: 047a99ed2d3f76c7d54e5bacaac1c1d38d1b743c (Git master at the time I am writing this)

AUTHENTICATE arguments are not split.
When using a very long password, the authstring given with AUTHENTICATE exceeds 400 characters, and thus should be splitted, according to [the specification](http://ircv3.net/specs/extensions/sasl-3.1.html#the-authenticate-command)

Config file:

```
[core]
nick = Sopel
host = 0.0.0.0
use_ssl = false
port = 34827
owner = me
channels = 
auth_username = foo
auth_password = barbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbar
auth_method = sasl
```

Sopel session log:

```
C: CAP LS 302
S: CAP * LS :sasl
C: NICK Sopel
C: USER sopel +iw Sopel :Sopel: http://sopel.chat
C: CAP REQ multi-prefix
S: CAP Sopel NAK :multi-prefix
C: CAP REQ sasl
S: CAP Sopel ACK :sasl
C: AUTHENTICATE PLAIN
S: AUTHENTICATE +
C: AUTHENTICATE Zm9vAGZvbwBiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiY
```

Expected log:

```
C: CAP LS 302
S: CAP * LS :sasl
C: NICK Sopel
C: USER sopel +iw Sopel :Sopel: http://sopel.chat
C: CAP REQ multi-prefix
S: CAP Sopel NAK :multi-prefix
C: CAP REQ sasl
S: CAP Sopel ACK :sasl
C: AUTHENTICATE PLAIN
S: AUTHENTICATE +
C: AUTHENTICATE :Zm9vAGZvbwBiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJi
C: AUTHENTICATE :YXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJiYXJi
C: AUTHENTICATE :YXJiYXJiYXI=
```

Version: 047a99ed2d3f76c7d54e5bacaac1c1d38d1b743c (Git master at the time I am writing this)



Discussion:
Since I'm working on PR #1448 to manage multi-line messages, I think I can reuse some of the logic (if not the code itself) to handle this case. I'll see what I can do, and for now I just leave a note here.
Just a quick note for myself, here is an extract from the specification to implement:

> The response is encoded in Base64 (RFC 4648), then split to 400-byte chunks, and each chunk is sent as a separate AUTHENTICATE command. Empty (zero-length) responses are sent as AUTHENTICATE +. If the last chunk was exactly 400 bytes long, it must also be followed by AUTHENTICATE + to signal end of response.

I'll work on that.
Since I'm working on PR #1448 to manage multi-line messages, I think I can reuse some of the logic (if not the code itself) to handle this case. I'll see what I can do, and for now I just leave a note here.
Just a quick note for myself, here is an extract from the specification to implement:

> The response is encoded in Base64 (RFC 4648), then split to 400-byte chunks, and each chunk is sent as a separate AUTHENTICATE command. Empty (zero-length) responses are sent as AUTHENTICATE +. If the last chunk was exactly 400 bytes long, it must also be followed by AUTHENTICATE + to signal end of response.

I'll work on that.

Resolving patch:
diff --git a/sopel/coretasks.py b/sopel/coretasks.py
--- a/sopel/coretasks.py
+++ b/sopel/coretasks.py
@@ -539,6 +539,37 @@ def recieve_cap_ack_sasl(bot):
     bot.write(('AUTHENTICATE', mech))
 
 
+def send_authenticate(bot, token):
+    """Send ``AUTHENTICATE`` command to server with the given ``token``.
+
+    :param bot: instance of IRC bot that must authenticate
+    :param str token: authentication token
+
+    In case the ``token`` is more than 400 bytes, we need to split it and send
+    as many ``AUTHENTICATE`` commands as needed. If the last chunk is 400 bytes
+    long, we must also send a last empty command (`AUTHENTICATE +` is for empty
+    line), so the server knows we are done with ``AUTHENTICATE``.
+
+    .. seealso::
+
+        https://ircv3.net/specs/extensions/sasl-3.1.html#the-authenticate-command
+
+    """
+    # payload is a base64 encoded token
+    payload = base64.b64encode(token.encode('utf-8'))
+
+    # split the payload into chunks of at most 400 bytes
+    chunk_size = 400
+    for i in range(0, len(payload), chunk_size):
+        offset = i + chunk_size
+        chunk = payload[i:offset]
+        bot.write(('AUTHENTICATE', chunk))
+
+    # send empty (+) AUTHENTICATE when payload's length is a multiple of 400
+    if len(payload) % chunk_size == 0:
+        bot.write(('AUTHENTICATE', '+'))
+
+
 @sopel.module.event('AUTHENTICATE')
 @sopel.module.rule('.*')
 def auth_proceed(bot, trigger):
@@ -549,8 +580,7 @@ def auth_proceed(bot, trigger):
     sasl_username = bot.config.core.auth_username or bot.nick
     sasl_password = bot.config.core.auth_password
     sasl_token = '\0'.join((sasl_username, sasl_username, sasl_password))
-    # Spec says we do a base 64 encode on the SASL stuff
-    bot.write(('AUTHENTICATE', base64.b64encode(sasl_token.encode('utf-8'))))
+    send_authenticate(bot, sasl_token)
 
 
 @sopel.module.event(events.RPL_SASLSUCCESS)


Test patch: