Repository: sopel-irc/sopel

Issue:
Stuck for 4 minutes after receiving ERR_SASLFAIL
Raw logs:

```
>>1450554176.3839703    CAP LS 302
>>1450554176.3842342    NICK Sopel
>>1450554176.3844688    USER sopel +iw Sopel :Sopel: http://sopel.chat
<<1450554176.3851945    CAP * LS :sasl=EXTERNAL
>>1450554176.3856113    CAP REQ multi-prefix
>>1450554176.3857372    CAP REQ sasl
<<1450554176.4237819    CAP Sopel NAK :multi-prefix
<<1450554176.463731 
<<1450554176.4646215    CAP Sopel ACK :sasl
>>1450554176.4651208    AUTHENTICATE PLAIN
<<1450554176.4657657    904 Sopel :SASL auth failed
<<1450554176.5035148    
>>1450554296.4777203    PING 0.0.0.0
```

Expected behavior: either `CAP END` or close the connection after `904` (`ERR_SASLFAIL`).

Config:

```
[core]
nick = Sopel
host = 0.0.0.0
use_ssl = false
port = 49200
owner = me
channels = 
auth_username = jilles
auth_password = sesame
auth_method = sasl
```

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

Tries to use capabilities and SASL mechanisms that are not available
Hi,

I just ran Sopel 6.6.9 with this config:

```
[core]
nick = Sopel
host = 0.0.0.0
use_ssl = false
port = 49657
owner = me
channels = 
timeout = 5
auth_username = jilles
auth_password = sesame
auth_method = sasl
```

When connecting to a server that advertises it supports only `sasl=EXTERNAL`, Sopel requests a bunch of capabilities and tries to use PLAIN anyway:

```
1575920338.485 C: CAP LS 302
1575920338.485 S: CAP * LS :sasl=EXTERNAL
1575920338.485 C: NICK Sopel
1575920338.485 C: USER sopel +iw Sopel :Sopel: https://sopel.chat
1575920338.486 C: CAP REQ multi-prefix
1575920338.486 S: CAP Sopel NAK :multi-prefix
1575920338.486 C: CAP REQ away-notify
1575920338.486 S: CAP Sopel NAK :away-notify
1575920338.486 C: CAP REQ cap-notify
1575920338.486 S: CAP Sopel NAK :cap-notify
1575920338.486 C: CAP REQ server-time
1575920338.486 S: CAP Sopel NAK :server-time
1575920338.486 C: CAP REQ account-notify
1575920338.486 S: CAP Sopel NAK :account-notify
1575920338.486 C: CAP REQ extended-join
1575920338.486 S: CAP Sopel NAK :extended-join
1575920338.486 C: CAP REQ account-tag
1575920338.486 S: CAP Sopel NAK :account-tag
1575920338.486 C: CAP REQ sasl
1575920338.486 S: CAP Sopel ACK :sasl
1575920338.530 C: AUTHENTICATE PLAIN
```

Is this intended behavior?


Discussion:
That's an interesting bug to fix, but since it's from 2015 I can assume it's a "low priority".
Thank you me from 2019, now I should take the time to fix that now that I know more about auth and stuff.

My guess is: after such event, Sopel should, indeed, report the issue and close the connection itself, with a big error message in the logs.
> Is this intended behavior?

Only in that the code assumes `PLAIN` if no mechanism is specified in the config file. (This is slightly updated from 6.6.9, as the forthcoming 7.0 release supports separate server/nick authentication.) https://github.com/sopel-irc/sopel/blob/f4db54fa3731e9021044d210d0d2c0fa03324e43/sopel/coretasks.py#L603-L611

If you specify `auth_target = EXTERNAL` yourself in the config, does authentication then work? (I'm honestly not sure if anyone has ever tested non-PLAIN mechanisms.)

A lot of work has gone into building a foundation for Sopel to handle feature flags/tokens better at runtime (e.g. servers' `MYINFO` and `ISUPPORT` replies) in 7.0—which is due out later this month! (🤞 🚀) Improving the capability negotiation is a good next step, so I'll drop this in 7.1 as a "Tweak" (enhancement) to work on.
@ProgVal reading the code for Sopel 6.6.9, I think you need to define `auth_target` in your configuration file (under the `[core]` section).

See also: https://github.com/sopel-irc/sopel/blob/6.6.x/sopel/coretasks.py#L574-L581
Apparently I started working on this a while back and forgot about it. Partial "fix" (more of a workaround) is in #1928.
@ProgVal If you're still interested, I spruced up #1928 and it should be ready to have stuff thrown at it.

Resolving patch:
diff --git a/sopel/coretasks.py b/sopel/coretasks.py
--- a/sopel/coretasks.py
+++ b/sopel/coretasks.py
@@ -681,7 +681,11 @@ def receive_cap_list(bot, trigger):
                 if req.success:
                     req.success(bot, req.prefix + trigger)
             if cap == 'sasl':  # TODO why is this not done with bot.cap_req?
-                receive_cap_ack_sasl(bot)
+                try:
+                    receive_cap_ack_sasl(bot)
+                except ConfigurationError as error:
+                    LOGGER.error(str(error))
+                    bot.quit('Wrong SASL configuration.')
 
 
 def receive_cap_ls_reply(bot, trigger):
@@ -845,6 +849,19 @@ def sasl_success(bot, trigger):
     bot.write(('CAP', 'END'))
 
 
+@plugin.event(events.ERR_SASLFAIL)
+@plugin.event(events.ERR_SASLTOOLONG)
+@plugin.event(events.ERR_SASLABORTED)
+@plugin.event(events.ERR_NICKLOCKED)
+@plugin.unblockable
+@plugin.thread(False)
+def sasl_fail(bot, trigger):
+    """SASL Auth Failed: log the error and quit."""
+    LOGGER.error(
+        'SASL Auth Failed; check your configuration: %s', str(trigger))
+    bot.quit('SASL Auth Failed')
+
+
 @module.event(events.RPL_SASLMECHS)
 @module.unblockable
 def sasl_mechs(bot, trigger):
@@ -873,10 +890,13 @@ def sasl_mechs(bot, trigger):
 
         See https://github.com/sopel-irc/sopel/issues/1780 for background
         """
-        raise ConfigurationError(
-            "Configured SASL mechanism '{}' is not advertised by this server. "
-            "Advertised values: {}"
-            .format(mech, ', '.join(supported_mechs)))
+        LOGGER.error(
+            "Configured SASL mechanism '%s' is not advertised by this server. "
+            "Advertised values: %s",
+            mech,
+            ', '.join(supported_mechs),
+        )
+        bot.quit('Wrong SASL configuration.')
 
 
 def _get_sasl_pass_and_mech(bot):


Test patch: