From 4cffbaec3a551faf441067b3b017438f4fddd3cc Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Sun, 20 Nov 2022 00:45:44 -0800 Subject: [PATCH 0/7] *** NOT A PATCH *** *** BLURB HERE *** F. Jason Park (7): Add GS2 authorization to sasl-scram-rfc Don't set erc-networks--id until network is known Make erc--server-reconnecting non-buffer-local Support local ERC modules in erc-mode buffers Call erc-login indirectly via new generic wrapper Add non-IRCv3 SASL module to ERC Accept functions in place of passwords in ERC doc/misc/erc.texi | 162 ++++++- etc/ERC-NEWS | 13 +- lisp/erc/erc-backend.el | 33 +- lisp/erc/erc-common.el | 56 ++- lisp/erc/erc-compat.el | 104 +++++ lisp/erc/erc-goodies.el | 1 + lisp/erc/erc-networks.el | 53 ++- lisp/erc/erc-sasl.el | 429 ++++++++++++++++++ lisp/erc/erc-services.el | 5 +- lisp/erc/erc.el | 150 ++++-- lisp/net/sasl-scram-rfc.el | 21 +- test/lisp/erc/erc-sasl-tests.el | 344 ++++++++++++++ .../erc-scenarios-base-association-nick.el | 84 ++-- test/lisp/erc/erc-scenarios-sasl.el | 202 +++++++++ test/lisp/erc/erc-services-tests.el | 16 +- test/lisp/erc/erc-tests.el | 83 ++++ test/lisp/erc/resources/sasl/external.eld | 33 ++ test/lisp/erc/resources/sasl/plain-failed.eld | 16 + test/lisp/erc/resources/sasl/plain.eld | 39 ++ test/lisp/erc/resources/sasl/scram-sha-1.eld | 47 ++ .../lisp/erc/resources/sasl/scram-sha-256.eld | 47 ++ 21 files changed, 1793 insertions(+), 145 deletions(-) create mode 100644 lisp/erc/erc-sasl.el create mode 100644 test/lisp/erc/erc-sasl-tests.el create mode 100644 test/lisp/erc/erc-scenarios-sasl.el create mode 100644 test/lisp/erc/resources/sasl/external.eld create mode 100644 test/lisp/erc/resources/sasl/plain-failed.eld create mode 100644 test/lisp/erc/resources/sasl/plain.eld create mode 100644 test/lisp/erc/resources/sasl/scram-sha-1.eld create mode 100644 test/lisp/erc/resources/sasl/scram-sha-256.eld Interdiff: diff --git a/lisp/erc/erc-backend.el b/lisp/erc/erc-backend.el index ed1a92867b..3cd949ddd3 100644 --- a/lisp/erc/erc-backend.el +++ b/lisp/erc/erc-backend.el @@ -161,6 +161,7 @@ erc-whowas-on-nosuchnick (declare-function erc-login "erc" nil) (declare-function erc-make-notice "erc" (message)) (declare-function erc-network "erc-networks" nil) +(declare-function erc-networks--id-ensure-reload "erc-networks" (nid)) (declare-function erc-networks--id-given "erc-networks" (arg &rest args)) (declare-function erc-networks--id-reload "erc-networks" (arg &rest args)) (declare-function erc-nickname-in-use "erc" (nick reason)) @@ -1637,9 +1638,7 @@ define-erc-response-handler 'NICK-you ?n nick ?N nn) (run-hook-with-args 'erc-nick-changed-functions nn nick)) (t - (unless (or erc-server-connected - (erc-networks--id-given erc-networks--id)) - (setq erc-networks--id nil)) + (erc-networks--id-ensure-reload erc-networks--id) (erc-handle-user-status-change 'nick (list nick login host) (list nn)) (erc-display-message parsed 'notice bufs 'NICK ?n nick ?u login ?h host ?N nn)))))) @@ -2266,8 +2265,7 @@ erc-server-322-message (define-erc-response-handler (433) "Login-time \"nick in use\"." nil - (unless (or erc-server-connected (erc-networks--id-given erc-networks--id)) - (setq erc-networks--id nil)) + (erc-networks--id-ensure-reload erc-networks--id) (erc-nickname-in-use (cadr (erc-response.command-args parsed)) "already in use")) diff --git a/lisp/erc/erc-networks.el b/lisp/erc/erc-networks.el index 100d5b4b58..fed9b3591b 100644 --- a/lisp/erc/erc-networks.el +++ b/lisp/erc/erc-networks.el @@ -985,6 +985,20 @@ erc-networks--id-reload ((not (equal (buffer-name) new-name)))) (rename-buffer new-name 'unique)))) +(cl-defgeneric erc-networks--id-ensure-reload (_nid) + "Schedule or run a reload for IDs derived from nicks." + nil) + +(cl-defmethod erc-networks--id-ensure-reload + ((nid erc-networks--id-qualifying)) + (if erc-server-connected + (erc-networks--id-reload nid) + (letrec ((fn (lambda (&rest _) + (when (eq nid erc-networks--id) + (erc-networks--id-reload nid)) + (remove-hook 'erc-after-connect fn t)))) + (add-hook 'erc-after-connect fn nil t)))) + (cl-defgeneric erc-networks--id-ensure-comparable (self other) "Take measures to ensure two net identities are in comparable states.") diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el index 0b1f44e560..8daf8397d1 100644 --- a/lisp/erc/erc.el +++ b/lisp/erc/erc.el @@ -1918,16 +1918,23 @@ erc-setup-buffer (display-buffer buffer) (switch-to-buffer buffer))))) -(defun erc--local-module-modes (old-vars new) - ;; Emacs 27 doesnt' have `local-minor-modes' - (if-let* ((old-vars) - (fn (pcase-lambda (`(,k . ,v)) - (and (string-prefix-p "erc-" (symbol-name k)) - (string-suffix-p "-mode" (symbol-name k)) - v - k)))) - (delete-dups (append (delq nil (mapcar fn old-vars)) new nil)) - new)) +(defun erc--merge-local-modes (new-modes old-vars) + "Return a cons of two lists, each containing local-module modes. +In the first, put modes to be enabled in a new ERC buffer by +calling their associated functions. In the second, put modes to +be marked as disabled by setting their associated variables to +nil." + (if old-vars + (let ((out (list (reverse new-modes)))) + (pcase-dolist (`(,k . ,v) old-vars) + (when (and (string-prefix-p "erc-" (symbol-name k)) + (string-suffix-p "-mode" (symbol-name k))) + (if v + (cl-pushnew k (car out)) + (setf (car out) (delq k (car out))) + (cl-pushnew k (cdr out))))) + (cons (nreverse (car out)) (nreverse (cdr out)))) + (list new-modes))) (defun erc-open (&optional server port nick full-name connect passwd tgt-list channel process @@ -1966,8 +1973,8 @@ erc-open (when connect (run-hook-with-args 'erc-before-connect server port nick)) (set-buffer buffer) (setq old-point (point)) - (setq delayed-modules (erc--local-module-modes erc--server-reconnecting - (erc--update-modules))) + (setq delayed-modules (erc--merge-local-modes (erc--update-modules) + erc--server-reconnecting)) (delay-mode-hooks (erc-mode)) @@ -2041,7 +2048,8 @@ erc-open (erc-determine-parameters server port nick full-name user passwd) (save-excursion (run-mode-hooks)) - (dolist (mod delayed-modules) (funcall mod +1)) + (dolist (mod (car delayed-modules)) (funcall mod +1)) + (dolist (var (cdr delayed-modules)) (set var nil)) ;; set up prompt (unless continued-session @@ -5938,7 +5946,12 @@ erc-set-current-nick (with-current-buffer (if (buffer-live-p (erc-server-buffer)) (erc-server-buffer) (current-buffer)) - (setq erc-server-current-nick nick))) + (unless (equal erc-server-current-nick nick) + (setq erc-server-current-nick nick) + ;; This seems sensible but may well be superfluous. Should + ;; really prove that it's actually needed via test scenario. + (erc-networks--id-ensure-reload erc-networks--id)) + nick)) (defun erc-current-nick () "Return the current nickname." diff --git a/test/lisp/erc/erc-scenarios-base-association-nick.el b/test/lisp/erc/erc-scenarios-base-association-nick.el index 3e848be4df..b46c996bc0 100644 --- a/test/lisp/erc/erc-scenarios-base-association-nick.el +++ b/test/lisp/erc/erc-scenarios-base-association-nick.el @@ -25,13 +25,24 @@ (eval-when-compile (require 'erc-join)) -;; You register a new nick, disconnect, and log back in, but your nick -;; is not granted, so ERC obtains a backtick'd version. You open a -;; query buffer for NickServ, and ERC names it using the net-ID (which -;; includes the backtick'd nick) as a suffix. The original -;; (disconnected) NickServ buffer gets renamed with *its* net-ID as -;; well. You then identify to NickServ, and the dead session is no -;; longer considered distinct. +;; You register a new nick in a dedicated query buffer, disconnect, +;; and log back in, but your nick is not granted (maybe you just +;; turned off SASL). In any case, ERC obtains a backtick'd version. +;; You open a query buffer for NickServ, and ERC gives you the +;; existing one. And after you identify, all buffers retain their +;; names, although your net ID has changed internally. +;; +;; If ERC would've instead failed (or intentionally refused) to make +;; the association, you would've ended up with a new NickServ buffer +;; named after the new net ID as a suffix (based on the backtick'd +;; nick), for example, NickServ@foonet/tester`. And the original +;; (disconnected) NickServ buffer would've gotten suffixed with *its* +;; net-ID as well, e.g., NickServ@foonet/tester. And after +;; identifying, you would've seen ERC merge the two as well as their +;; server buffers. While this alternate behavior may arguably be a +;; more honest reflection of reality, it's also quite inconvenient. +;; For a clearer example, see the original version of this file +;; introduced by "Add user-oriented test scenarios for ERC". (ert-deftest erc-scenarios-base-association-nick-bumped () :tags '(:expensive-test) @@ -67,30 +78,29 @@ erc-scenarios-base-association-nick-bumped (funcall expect 5 "ERC finished")))) (with-current-buffer "foonet" - (erc-cmd-RECONNECT)) + (erc-cmd-RECONNECT) + (funcall expect 10 "User modes for tester`")) - (erc-d-t-wait-for 10 "Nick request rejection prevents reassociation (good)" - (get-buffer "foonet/tester`")) + (ert-info ("Server buffer reassociated with new nick") + (should-not (get-buffer "foonet/tester`"))) (ert-info ("Ask NickServ to change nick") - (with-current-buffer "foonet/tester`" - (funcall expect 3 "already in use") + (with-current-buffer "foonet" (funcall expect 3 "debug mode") (erc-cmd-QUERY "NickServ")) - (erc-d-t-wait-for 1 "Dead NickServ query buffer renamed, now qualified" - (get-buffer "NickServ@foonet/tester")) + (ert-info ( "NickServ buffer reassociated") + (should-not (get-buffer "NickServ@foonet/tester`")) + (should-not (get-buffer "NickServ@foonet/tester"))) - (with-current-buffer "NickServ@foonet/tester`" ; new one + (with-current-buffer "NickServ" ; new one (erc-scenarios-common-say "IDENTIFY tester changeme") - (funcall expect 5 "You're now logged in as tester") - (ert-info ("Original buffer found, reused") - (erc-d-t-wait-for 2 (equal (buffer-name) "NickServ"))))) + (funcall expect 5 "You're now logged in as tester"))) - (ert-info ("Ours is the only NickServ buffer that remains") + (ert-info ("Still just one NickServ buffer") (should-not (cdr (erc-scenarios-common-buflist "NickServ")))) - (ert-info ("Visible network ID truncated to one component") + (ert-info ("As well as one server buffer") (should (not (get-buffer "foonet/tester`"))) (should (not (get-buffer "foonet/tester"))) (should (get-buffer "foonet"))))) @@ -135,29 +145,29 @@ erc-scenarios-base-association-nick-bumped-mandated-renick ;; Since we use reconnect, a new buffer won't be created ;; TODO add variant with clean `erc' invocation (with-current-buffer "foonet" - (erc-cmd-RECONNECT)) + (erc-cmd-RECONNECT) + (funcall expect 10 "User modes for dummy")) - (ert-info ("Server-initiated renick") - (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "foonet/dummy")) - (should-not (get-buffer "foonet/tester")) - (funcall expect 15 "debug mode")) + (ert-info ("Server-initiated renick associated correctly") + (with-current-buffer "foonet" + (funcall expect 15 "debug mode") + (should-not (get-buffer "foonet/dummy")) + (should-not (get-buffer "foonet/tester"))) - (erc-d-t-wait-for 1 "Old query renamed, now qualified" - (get-buffer "bob@foonet/tester")) + (ert-info ("Old query reassociated") + (should (get-buffer "bob")) + (should-not (get-buffer "bob@foonet/tester")) + (should-not (get-buffer "bob@foonet/dummy"))) - (with-current-buffer (erc-d-t-wait-for 5 (get-buffer "bob@foonet/dummy")) + (with-current-buffer "foonet" (erc-cmd-NICK "tester") - (ert-info ("Buffers combined") - (erc-d-t-wait-for 2 (equal (buffer-name) "bob"))))) + (funcall expect 5 "You're now logged in as tester"))) - (with-current-buffer "foonet" - (funcall expect 5 "You're now logged in as tester")) - - (ert-info ("Ours is the only bob buffer that remains") + (ert-info ("Ours is still the only bob buffer that remains") (should-not (cdr (erc-scenarios-common-buflist "bob")))) - (ert-info ("Visible network ID truncated to one component") - (should (not (get-buffer "foonet/dummy"))) - (should (get-buffer "foonet"))))) + (ert-info ("Visible network ID still truncated to one component") + (should (not (get-buffer "foonet/tester"))) + (should (not (get-buffer "foonet/dummy")))))) ;;; erc-scenarios-base-association-nick.el ends here diff --git a/test/lisp/erc/erc-tests.el b/test/lisp/erc/erc-tests.el index 4e646a775b..c74c2e4747 100644 --- a/test/lisp/erc/erc-tests.el +++ b/test/lisp/erc/erc-tests.el @@ -1247,22 +1247,18 @@ erc--update-modules erc-networks (networks . 1)))) (setq calls nil))))) -(ert-deftest erc--local-module-modes () - - (should (equal (erc--local-module-modes nil '(erc-a-mode erc-b-mode)) - '(erc-a-mode erc-b-mode))) - - (should (equal (erc--local-module-modes - '((a) (b . t)) '(erc-c-mode erc-d-mode)) - '(erc-c-mode erc-d-mode))) - - (should (equal (erc--local-module-modes - '((a) (erc-b-mode) (c . t) (erc-d-mode . t) (erc-e-mode . t)) - '(erc-b-mode erc-d-mode)) - '(erc-d-mode erc-e-mode erc-b-mode))) - - (should (equal (erc--local-module-modes - '((a) (b . t) (erc-e-mode . t)) nil) - '(erc-e-mode)))) +(ert-deftest erc--merge-local-modes () + + (ert-info ("No existing modes") + (let ((old '((a) (b . t))) + (new '(erc-c-mode erc-d-mode))) + (should (equal (erc--merge-local-modes new old) + '((erc-c-mode erc-d-mode)))))) + + (ert-info ("Active existing added, inactive existing removed, deduped") + (let ((old '((a) (erc-b-mode) (c . t) (erc-d-mode . t) (erc-e-mode . t))) + (new '(erc-b-mode erc-d-mode))) + (should (equal (erc--merge-local-modes new old) + '((erc-d-mode erc-e-mode) . (erc-b-mode))))))) ;;; erc-tests.el ends here -- 2.38.1