From bef06cab7e2fa695e69e30eb097f44172dbc00e3 Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Tue, 22 Nov 2022 00:34:27 -0800 Subject: [PATCH 0/8] *** NOT A PATCH *** *** BLURB HERE *** F. Jason Park (8): 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 Add test scenarios for local ERC modules doc/misc/erc.texi | 190 +++++++- etc/ERC-NEWS | 16 +- lisp/erc/erc-backend.el | 43 +- lisp/erc/erc-common.el | 82 +++- lisp/erc/erc-compat.el | 97 +++- lisp/erc/erc-goodies.el | 1 + lisp/erc/erc-networks.el | 53 +-- lisp/erc/erc-sasl.el | 417 ++++++++++++++++++ lisp/erc/erc-services.el | 5 +- lisp/erc/erc.el | 166 ++++--- lisp/net/sasl-scram-rfc.el | 21 +- test/lisp/erc/erc-sasl-tests.el | 344 +++++++++++++++ .../erc-scenarios-base-association-nick.el | 84 ++-- .../erc/erc-scenarios-base-local-modules.el | 243 ++++++++++ test/lisp/erc/erc-scenarios-sasl.el | 144 ++++++ test/lisp/erc/erc-services-tests.el | 16 +- test/lisp/erc/erc-tests.el | 178 ++++++++ .../resources/base/local-modules/first.eld | 53 +++ .../resources/base/local-modules/fourth.eld | 53 +++ .../resources/base/local-modules/second.eld | 47 ++ .../resources/base/local-modules/third.eld | 43 ++ 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 ++ 26 files changed, 2314 insertions(+), 164 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-base-local-modules.el create mode 100644 test/lisp/erc/erc-scenarios-sasl.el create mode 100644 test/lisp/erc/resources/base/local-modules/first.eld create mode 100644 test/lisp/erc/resources/base/local-modules/fourth.eld create mode 100644 test/lisp/erc/resources/base/local-modules/second.eld create mode 100644 test/lisp/erc/resources/base/local-modules/third.eld 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/doc/misc/erc.texi b/doc/misc/erc.texi index 0c3137999a..f86465fed7 100644 --- a/doc/misc/erc.texi +++ b/doc/misc/erc.texi @@ -391,11 +391,11 @@ Modules There is a spiffy customize interface, which may be reached by typing @kbd{M-x customize-option @key{RET} erc-modules @key{RET}}. -When removing a module outside of the Customize ecosystem, you may -wish to ensure it's disabled by invoking its associated minor-mode -toggle, such as @kbd{M-x erc-spelling-mode @key{RET}}. It may also be -worth noting that, these days, calling @code{erc-update-modules} in an -init file is typically unnecessary. +When removing a module outside of the Custom ecosystem, you may wish +to ensure it's disabled by invoking its associated minor-mode toggle, +such as @kbd{M-x erc-spelling-mode @key{RET}}. Note that, these days, +calling @code{erc-update-modules} in an init file is typically +unnecessary. The following is a list of available modules. @@ -526,24 +526,26 @@ Modules @end table @subheading Local Modules +@cindex local modules All modules operate as minor modes under the hood, and some newer ones -may be defined as buffer-local. For everyday use, the only practical -differences are +may be defined as buffer-local. These so-called ``local modules'' are +a work in progress and their behavior and interface are subject to +change. As of ERC 5.5, the only practical differences are @enumerate @item -``control variables,'' like @code{erc-sasl-mode}, are stateful across +``Control variables,'' like @code{erc-sasl-mode}, are stateful across IRC sessions and override @code{erc-module} membership when influencing -module activation in new sessions +module activation in new sessions. @item -removing a local module from @code{erc-modules} via Customize not only +Removing a local module from @code{erc-modules} via Customize not only disables its mode but also kills its control variable in all ERC -buffers +buffers. @item -``toggle commands,'' like @code{erc-sasl-mode} and -@code{erc-sasl-enable}, behave differently, both from each other and -from their global counterparts +``Mode toggles,'' like @code{erc-sasl-mode} and +@code{erc-sasl-enable}, behave differently relative to each other and +to their global counterparts. (More on this just below.) @end enumerate By default, all local-mode toggles, like @code{erc-sasl-mode}, only diff --git a/etc/ERC-NEWS b/etc/ERC-NEWS index 3e1b7bca95..d0d84d0a98 100644 --- a/etc/ERC-NEWS +++ b/etc/ERC-NEWS @@ -130,6 +130,9 @@ connection-related ones in erc-backend). 'erc-open' also no longer calls 'erc-update-modules', although modules are still activated in an identical fashion. +Some groundwork has been laid for what may become a new breed of ERC +module, namely, "connection-local" (or simply "local") modules. + A few internal variables have been introduced that could just as well have been made public, possibly as user options. Likewise for some internal functions. As always, users needing such functionality diff --git a/lisp/erc/erc-backend.el b/lisp/erc/erc-backend.el index f387491d4c..43c5faad63 100644 --- a/lisp/erc/erc-backend.el +++ b/lisp/erc/erc-backend.el @@ -2335,6 +2335,15 @@ erc-server-322-message (erc-display-message parsed 'notice 'active 's671 ?n nick ?a securemsg))) +(define-erc-response-handler (900) + "Handle a \"RPL_LOGGEDIN\" server command. +Some servers don't consider this SASL-specific but rather just an +indication of a server-side state change from logged-out to +logged-in." nil + ;; Whenever ERC starts caring about user accounts, it should record + ;; the session as being logged here. + (erc-display-message parsed 'notice proc (erc-response.contents parsed))) + (define-erc-response-handler (431 445 446 451 462 463 464 481 483 484 485 491 501 502) ;; 431 - No nickname given diff --git a/lisp/erc/erc-compat.el b/lisp/erc/erc-compat.el index 8b95f8ac81..66a9a615e3 100644 --- a/lisp/erc/erc-compat.el +++ b/lisp/erc/erc-compat.el @@ -252,8 +252,18 @@ erc-compat--29-auth-source-pass-search ;; From `auth-source-pass-search' (cl-assert (and host (not (eq host t))) t "Invalid password-store search: %s %s") - (erc-compat--29-auth-source-pass--build-result-many - host user port require max)) + (let ((rv (erc-compat--29-auth-source-pass--build-result-many + host user port require max))) + (if (and (fboundp 'auth-source--obfuscate) + (fboundp 'auth-source--deobfuscate)) + (let (out) + (dolist (e rv out) + (when-let* ((s (plist-get e :secret)) + (v (auth-source--obfuscate s))) + (setf (plist-get e :secret) + (byte-compile (lambda () (auth-source--deobfuscate v))))) + (push e out))) + rv))) (defun erc-compat--29-auth-source-pass-backend-parse (entry) (when (eq entry 'password-store) @@ -291,38 +301,17 @@ erc-compat--auth-source-backend-parser-functions (client)) (declare-function cl-mapcar "cl-lib" (cl-func cl-x &rest cl-rest)) -(defun erc-compat--sasl-scram-construct-gs2-header (client) - ;; The "n," means the client doesn't support channel binding, and - ;; the trailing comma is included as per RFC 5801. +(defun erc-compat--29-sasl-scram-construct-gs2-header (client) (let ((authzid (sasl-client-property client 'authenticator-name))) (concat "n," (and authzid "a=") authzid ","))) -(defun erc-compat--sasl-scram-client-first-message (client _step) +(defun erc-compat--29-sasl-scram-client-first-message (client _step) (let ((c-nonce (sasl-unique-id))) (sasl-client-set-property client 'c-nonce c-nonce)) - (concat (erc-compat--sasl-scram-construct-gs2-header client) + (concat (erc-compat--29-sasl-scram-construct-gs2-header client) (sasl-scram--client-first-message-bare client))) -;; This is `sasl-scram--client-final-message' from sasl-scram-rfc, -;; with the NO-LINE-BREAK argument of `base64-encode-string' set to t -;; because https://www.rfc-editor.org/rfc/rfc5802#section-2.1 says: -;; -;; > The use of base64 in SCRAM is restricted to the canonical form -;; > with no whitespace. -;; -;; Unfortunately, simply advising `base64-encode-string' won't work -;; since the byte compiler precomputes the result when all inputs are -;; constants, as they are in the original version. -;; -;; The only other substantial change is the addition of authz support. -;; This can be dropped if adopted by Emacs 29 and `compat'. Changes -;; proposed for 29 are marked with a "; *n", comment below. See older -;; versions of lisp/erc/erc-v3-sasl.el (bug#49860) if needing a true -;; side-by-side diff. This also inlines the internal function -;; `sasl-scram--client-first-message-bare' and takes various liberties -;; with formatting. - -(defun erc-compat--sasl-scram--client-final-message +(defun erc-compat--29-sasl-scram--client-final-message (hash-fun block-length hash-length client step) (unless (string-match "^r=\\([^,]+\\),s=\\([^,]+\\),i=\\([0-9]+\\)\\(?:$\\|,\\)" @@ -339,7 +328,7 @@ erc-compat--sasl-scram--client-final-message (c-nonce (sasl-client-property client 'c-nonce)) (cbind-input (if (string-prefix-p c-nonce nonce) - (erc-compat--sasl-scram-construct-gs2-header client) ; *1 + (erc-compat--29-sasl-scram-construct-gs2-header client) ; *1 (sasl-error "Invalid nonce from server"))) (client-final-message-without-proof (concat "c=" (base64-encode-string cbind-input t) "," ; *2 diff --git a/lisp/erc/erc-sasl.el b/lisp/erc/erc-sasl.el index 0158161b84..9084d873ce 100644 --- a/lisp/erc/erc-sasl.el +++ b/lisp/erc/erc-sasl.el @@ -150,7 +150,7 @@ erc-sasl--plain-response (let ((sasl-read-passphrase #'erc-sasl--read-password)) (sasl-plain-response client steps))) -(declare-function erc-compat--sasl-scram--client-final-message "erc-compat" +(declare-function erc-compat--29-sasl-scram--client-final-message "erc-compat" (hash-fun block-length hash-length client step)) (defun erc-sasl--scram-sha-hack-client-final-message (&rest args) @@ -158,7 +158,7 @@ erc-sasl--scram-sha-hack-client-final-message ;; `sasl-scram--client-final-message' directly (require 'erc-compat) (let ((sasl-read-passphrase #'erc-sasl--read-password)) - (apply #'erc-compat--sasl-scram--client-final-message args))) + (apply #'erc-compat--29-sasl-scram--client-final-message args))) (defun erc-sasl--scram-sha-1-client-final-message (client step) (erc-sasl--scram-sha-hack-client-final-message 'sha1 64 20 client step)) @@ -202,15 +202,15 @@ erc-sasl--ecdsa-sign ("EXTERNAL" ignore) ("SCRAM-SHA-1" - erc-compat--sasl-scram-client-first-message + erc-compat--29-sasl-scram-client-first-message erc-sasl--scram-sha-1-client-final-message sasl-scram-sha-1-authenticate-server) ("SCRAM-SHA-256" - erc-compat--sasl-scram-client-first-message + erc-compat--29-sasl-scram-client-first-message erc-sasl--scram-sha-256-client-final-message sasl-scram-sha-256-authenticate-server) ("SCRAM-SHA-512" - erc-compat--sasl-scram-client-first-message + erc-compat--29-sasl-scram-client-first-message erc-sasl--scram-sha-512-client-final-message erc-sasl--scram-sha-512-authenticate-server) ("ECDSA-NIST256P-CHALLENGE" @@ -303,36 +303,6 @@ erc-sasl--mechanism-offered-p (| eot ","))) (downcase offered))) -(defun erc-sasl--authenticate-handler (_proc parsed) - "Handle PARSED `erc-response' from server. -Maybe transition to next state." - (if-let* ((response (car (erc-response.command-args parsed))) - ((= 400 (length response)))) - (cl-callf (lambda (s) (concat s response)) - (erc-sasl--state-pending erc-sasl--state)) - (cl-assert response t) - (when (string= "+" response) - (setq response "")) - (setf response (base64-decode-string - (concat (erc-sasl--state-pending erc-sasl--state) - response)) - (erc-sasl--state-pending erc-sasl--state) nil) - ;; The server is done sending, so our turn - (let ((client (erc-sasl--state-client erc-sasl--state)) - (step (erc-sasl--state-step erc-sasl--state)) - data) - (when step - (sasl-step-set-data step response)) - (setq step (setf (erc-sasl--state-step erc-sasl--state) - (sasl-next-step client step)) - data (sasl-step-data step)) - (when (string= data "") - (setq data nil)) - (when data - (setq data (base64-encode-string data t))) - ;; No need for : because no spaces (right?) - (erc-server-send (concat "AUTHENTICATE " (or data "+")))))) - (erc-define-catalog 'english '((s902 . "ERR_NICKLOCKED nick %n unavailable: %s") @@ -347,8 +317,6 @@ sasl This doesn't solicit or validate a suite of supported mechanisms." ;; See bug#49860 for a CAP 3.2-aware WIP implementation. ((unless erc--target - (add-hook 'erc-server-AUTHENTICATE-functions - #'erc-sasl--authenticate-handler 0 t) (erc-sasl--init) (let* ((mech (alist-get 'mechanism erc-sasl--options)) (client (erc-sasl--create-client mech))) @@ -357,15 +325,36 @@ sasl nil (format "Unknown or unsupported SASL mechanism: %s" mech)) (erc-error "Unknown or unsupported SASL mechanism: %s" mech)) (setf (erc-sasl--state-client erc-sasl--state) client)))) - ((remove-hook 'erc-server-AUTHENTICATE-functions - #'erc-sasl--authenticate-handler t) - (kill-local-variable 'erc-sasl--state) + ((kill-local-variable 'erc-sasl--state) (kill-local-variable 'erc-sasl--options)) 'local) -;; FIXME use generic mechanism instead of hooks after bug#49860. (define-erc-response-handler (AUTHENTICATE) - "Maybe authenticate to server." nil) + "Begin or resume an SASL session." nil + (if-let* ((response (car (erc-response.command-args parsed))) + ((= 400 (length response)))) + (cl-callf (lambda (s) (concat s response)) + (erc-sasl--state-pending erc-sasl--state)) + (cl-assert response t) + (when (string= "+" response) + (setq response "")) + (setf response (base64-decode-string + (concat (erc-sasl--state-pending erc-sasl--state) + response)) + (erc-sasl--state-pending erc-sasl--state) nil) + (let ((client (erc-sasl--state-client erc-sasl--state)) + (step (erc-sasl--state-step erc-sasl--state)) + data) + (when step + (sasl-step-set-data step response)) + (setq step (setf (erc-sasl--state-step erc-sasl--state) + (sasl-next-step client step)) + data (sasl-step-data step)) + (when (string= data "") + (setq data nil)) + (when data + (setq data (erc--unfun (base64-encode-string data t)))) + (erc-server-send (concat "AUTHENTICATE " (or data "+")))))) (defun erc-sasl--destroy (proc) (run-hook-with-args 'erc-quit-hook proc) @@ -373,7 +362,7 @@ erc-sasl--destroy (erc-error "Disconnected from %s; please review SASL settings" proc)) (define-erc-response-handler (902) - "Handle a ERR_NICKLOCKED response." nil + "Handle an ERR_NICKLOCKED response." nil (erc-display-message parsed '(notice error) 'active 's902 ?n (car (erc-response.command-args parsed)) ?s (erc-response.contents parsed)) @@ -384,7 +373,7 @@ erc-sasl--destroy (when erc-sasl-mode (unless erc-server-connected (erc-server-send "CAP END"))) - (erc-handle-unknown-server-response proc parsed)) + (erc-display-message parsed 'notice proc (erc-response.contents parsed))) (define-erc-response-handler (907) "Handle a RPL_SASLALREADY response." nil diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el index f18e214d55..268d83dc44 100644 --- a/lisp/erc/erc.el +++ b/lisp/erc/erc.el @@ -2346,10 +2346,10 @@ erc--mask-secrets (end (text-property-not-all beg eot 'erc-secret t string)) (sec (substring string beg end))) (setq string (concat (substring string 0 beg) - (make-string (- end beg) ??) + (make-string 10 ??) (substring string end eot))) - (put-text-property beg end 'face 'erc-inverse-face string) - (put-text-property beg end 'display sec string)) + (put-text-property beg (+ 10 beg) 'face 'erc-inverse-face string) + (put-text-property beg (+ 10 beg) 'display sec string)) string) (defun erc-log-irc-protocol (string &optional outbound) diff --git a/test/lisp/erc/erc-sasl-tests.el b/test/lisp/erc/erc-sasl-tests.el index 20a6760083..64593ca270 100644 --- a/test/lisp/erc/erc-sasl-tests.el +++ b/test/lisp/erc/erc-sasl-tests.el @@ -141,7 +141,7 @@ erc-sasl-create-client--scram-sha-1 (ert-info ("Client's initial request") (let ((req "n,a=jilles,n=jilles,r=c5RqLCZy0L4fGkKAZ0hujFBs")) (should (equal (format "%S" - `[erc-compat--sasl-scram-client-first-message + `[erc-compat--29-sasl-scram-client-first-message ,req]) (format "%S" step))) (should (string= (sasl-step-data step) req)))) @@ -180,7 +180,7 @@ erc-sasl-create-client--scram-sha-256 (ert-info ("Client's initial request") (let ((req "n,a=jilles,n=jilles,r=c5RqLCZy0L4fGkKAZ0hujFBs")) (should (equal (format "%S" - `[erc-compat--sasl-scram-client-first-message + `[erc-compat--29-sasl-scram-client-first-message ,req]) (format "%S" step))) (should (string= (sasl-step-data step) req)))) @@ -220,7 +220,7 @@ erc-sasl-create-client--scram-sha-256--no-authzid (ert-info ("Client's initial request") (let ((req "n,,n=jilles,r=c5RqLCZy0L4fGkKAZ0hujFBs")) (should (equal (format "%S" - `[erc-compat--sasl-scram-client-first-message + `[erc-compat--29-sasl-scram-client-first-message ,req]) (format "%S" step))) (should (string= (sasl-step-data step) req)))) @@ -260,7 +260,7 @@ erc-sasl-create-client--scram-sha-512--no-authzid (ert-info ("Client's initial request") (let ((req "n,,n=jilles,r=c5RqLCZy0L4fGkKAZ0hujFBs")) (should (equal (format "%S" - `[erc-compat--sasl-scram-client-first-message + `[erc-compat--29-sasl-scram-client-first-message ,req]) (format "%S" step))) (should (string= (sasl-step-data step) req)))) -- 2.38.1