From 21f3196c0b55d8e7c27c4918f741cbbecfaf2136 Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Sun, 13 Nov 2022 07:14:27 -0800 Subject: [PATCH 0/5] *** NOT A PATCH *** *** BLURB HERE *** F. Jason Park (5): Add GS2 authorization to sasl-scram-rfc Don't set erc-networks--id until network is known Support local ERC modules in erc-mode buffers Call erc-login indirectly via new generic wrapper Add non-IRCv3 SASL module to ERC doc/misc/erc.texi | 137 +++++- lisp/erc/erc-backend.el | 15 +- lisp/erc/erc-common.el | 56 ++- lisp/erc/erc-compat.el | 116 +++++ lisp/erc/erc-goodies.el | 1 + lisp/erc/erc-networks.el | 39 +- lisp/erc/erc-sasl.el | 424 ++++++++++++++++++ lisp/erc/erc.el | 85 ++-- lisp/net/sasl-scram-rfc.el | 21 +- test/lisp/erc/erc-sasl-tests.el | 319 +++++++++++++ test/lisp/erc/erc-scenarios-sasl.el | 208 +++++++++ test/lisp/erc/erc-tests.el | 63 +++ 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 ++ 17 files changed, 1586 insertions(+), 80 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/doc/misc/erc.texi b/doc/misc/erc.texi index 80b4171cdb..79f8c92719 100644 --- a/doc/misc/erc.texi +++ b/doc/misc/erc.texi @@ -938,13 +938,12 @@ SASL know that you almost certainly won't be needing SASL for the @samp{client -> bouncer} connection. -Note that @code{sasl} is a ``local'' ERC module. This means invoking -@code{erc-sasl-mode} manually or calling @code{erc-update-modules} -won't do any good. Instead, simply add @code{sasl} to -@code{erc-modules} (or @code{let}-bind it while calling -@code{erc-tls}), and SASL will be enabled for the current connection. -But before that, please explore all custom options pertaining to your -chosen mechanism. +Note that @code{sasl} is a ``local'' ERC module, which various library +functions, like @code{erc-update-modules}, may treat differently than +global modules in user code. However, this should not affect everyday +client use. To get started, just add @code{sasl} to +@code{erc-modules} like any other module. But before that, please +explore all custom options pertaining to your chosen mechanism. @defopt erc-sasl-mechanism The name of an SASL subprotocol type as a @emph{lowercase} symbol. diff --git a/lisp/erc/erc-backend.el b/lisp/erc/erc-backend.el index fee29e7d05..37a3da8b66 100644 --- a/lisp/erc/erc-backend.el +++ b/lisp/erc/erc-backend.el @@ -1529,7 +1529,7 @@ define-erc-response-handler (cl-pushnew (erc-server-buffer) bufs) (erc-set-current-nick nn) ;; Rename session, possibly rename server buf and all targets - (when (erc-network) + (when erc-server-connected (erc-networks--id-reload erc-networks--id proc parsed)) (erc-update-mode-line) (setq erc-nick-change-attempt-count 0) @@ -1539,6 +1539,9 @@ 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-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)))))) @@ -2165,6 +2168,8 @@ 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-nickname-in-use (cadr (erc-response.command-args parsed)) "already in use")) diff --git a/lisp/erc/erc-common.el b/lisp/erc/erc-common.el index 90ea56108d..a300cfc4fa 100644 --- a/lisp/erc/erc-common.el +++ b/lisp/erc/erc-common.el @@ -28,7 +28,6 @@ (defvar erc--casemapping-rfc1459) (defvar erc--casemapping-rfc1459-strict) -(defvar erc--module-name-migrations) (defvar erc-channel-users) (defvar erc-dbuf) (defvar erc-log-p) @@ -86,9 +85,40 @@ erc--target (contents "" :type string) (tags '() :type list)) -(defun erc--normalize-module-symbol (module) - "Canonicalize symbol MODULE for `erc-modules'." - (or (cdr (assq module erc--module-name-migrations)) module)) +;; TODO move goodies modules here after 29 is released. +(defconst erc--features-to-modules + '((erc-pcomplete completion pcomplete) + (erc-capab capab-identify) + (erc-join autojoin) + (erc-page page ctcp-page) + (erc-sound sound ctcp-sound) + (erc-stamp stamp timestamp) + (erc-services services nickserv)) + "Migration alist mapping a library feature to module names. +Keys need not be unique: a library may define more than one +module. Sometimes a module's downcased alias will be its +canonical name.") + +(defconst erc--modules-to-features + (let (pairs) + (pcase-dolist (`(,feature . ,names) erc--features-to-modules) + (dolist (name names) + (push (cons name feature) pairs))) + (nreverse pairs)) + "Migration alist mapping a module's name to its home library feature.") + +(defconst erc--module-name-migrations + (let (pairs) + (pcase-dolist (`(,_ ,canonical . ,rest) erc--features-to-modules) + (dolist (obsolete rest) + (push (cons obsolete canonical) pairs))) + pairs) + "Association list of obsolete module names to canonical names.") + +(defun erc--normalize-module-symbol (symbol) + "Return preferred SYMBOL for `erc-modules'." + (setq symbol (intern (downcase (symbol-name symbol)))) + (or (cdr (assq symbol erc--module-name-migrations)) symbol)) (defmacro define-erc-module (name alias doc enable-body disable-body &optional local-p) @@ -118,6 +148,7 @@ define-erc-module #\\='erc-replace-insert)))" (declare (doc-string 3) (indent defun)) (let* ((sn (symbol-name name)) + (mod (erc--normalize-module-symbol name)) (mode (intern (format "erc-%s-mode" (downcase sn)))) (group (intern (format "erc-%s" (downcase sn)))) (enable (intern (format "erc-%s-enable" (downcase sn)))) @@ -141,21 +172,20 @@ define-erc-module ,(format "Enable ERC %S mode." name) (interactive) - (unless ,local-p - (cl-pushnew (erc--normalize-module-symbol ',name) erc-modules)) - (when (or ,(not local-p) (eq major-mode 'erc-mode)) - (setq ,mode t) - ,@enable-body)) + ,@(unless local-p `((cl-pushnew ',mod erc-modules))) + ,@(macroexp-unprogn + `(,@(if local-p '(when (eq major-mode 'erc-mode)) '(progn)) + (setq ,mode t) + ,@enable-body))) (defun ,disable () ,(format "Disable ERC %S mode." name) (interactive) - (unless ,local-p - (setq erc-modules (delq (erc--normalize-module-symbol ',name) - erc-modules))) - (when (or ,(not local-p) ,mode) - (setq ,mode nil) - ,@disable-body)) + ,@(unless local-p `((setq erc-modules (delq ',mod erc-modules)))) + ,@(macroexp-unprogn + `(,@(if local-p `(when ,mode) '(progn)) + (setq ,mode nil) + ,@disable-body))) ,(when (and alias (not (eq name alias))) `(defalias ',(intern diff --git a/lisp/erc/erc-compat.el b/lisp/erc/erc-compat.el index bc3e1dcfc6..6d4ef21383 100644 --- a/lisp/erc/erc-compat.el +++ b/lisp/erc/erc-compat.el @@ -194,9 +194,9 @@ erc-compat--sasl-scram-client-first-message ;; > The use of base64 in SCRAM is restricted to the canonical form ;; > with no whitespace. ;; -;; Unfortunately, advising `base64-encode-string' won't work -;; because the byte compiler precomputes the result when all inputs -;; are constants, as they are in the unpatched version. +;; 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 @@ -272,6 +272,18 @@ erc-compat--with-memoization `(cl--generic-with-memoization ,table ,@forms)) (t `(progn ,@forms)))) +(defun erc-compat--local-minor-modes () + (delq nil + (if (boundp 'local-minor-modes) + (mapcar (lambda (m) + (and (string-prefix-p "erc-" (symbol-name m)) m)) + local-minor-modes) + (mapcar (pcase-lambda (`(,k . _)) + (and (string-prefix-p "erc-" (symbol-name k)) + (string-suffix-p "-mode" (symbol-name k)) + k)) + (buffer-local-variables))))) + (provide 'erc-compat) ;;; erc-compat.el ends here diff --git a/lisp/erc/erc-networks.el b/lisp/erc/erc-networks.el index dba6ead073..aa90bb8479 100644 --- a/lisp/erc/erc-networks.el +++ b/lisp/erc/erc-networks.el @@ -826,12 +826,11 @@ erc-networks--id ;; For now, please use this instead of `erc-networks--id-fixed-p'. (cl-defgeneric erc-networks--id-given (net-id) - "Return the preassigned identifier for a network presence, if any. -This may have originated from an `:id' arg to entry-point commands -`erc-tls' or `erc'.") + "Return the preassigned identifier for a network context, if any. +When non-nil, assume NET-ID originated from an `:id' argument to +entry-point commands `erc-tls' or `erc'.") -(cl-defmethod erc-networks--id-given ((_ erc-networks--id)) - nil) +(cl-defmethod erc-networks--id-given (_) nil) ; _ may be nil (cl-defmethod erc-networks--id-given ((nid erc-networks--id-fixed)) (erc-networks--id-symbol nid)) @@ -866,22 +865,15 @@ erc-networks--id-create ((_ symbol) &context (erc-obsolete-var erc-reuse-buffers null)) (erc-networks--id-fixed-create (intern (buffer-name)))) -(cl-defgeneric erc-networks--id-on-connect (net-id) - "Update NET-ID `erc-networks--id' after connection params known. -This is typically during or just after MOTD.") - -(cl-defmethod erc-networks--id-on-connect ((_ erc-networks--id)) - nil) - -(cl-defmethod erc-networks--id-on-connect ((id erc-networks--id-qualifying)) - (erc-networks--id-qualifying-update id (erc-networks--id-qualifying-create))) - (cl-defgeneric erc-networks--id-equal-p (self other) - "Return non-nil when two network identities exhibit underlying equality. -SELF and OTHER are `erc-networks--id' struct instances. This -should normally be used only for ID recovery or merging, after -which no two identities should be `equal' (timestamps aside) that -aren't also `eq'.") + "Return non-nil when two network IDs exhibit underlying equality. +Expect SELF and OTHER to be `erc-networks--id' struct instances +and that this will only be called for ID recovery or merging, +after which no two identities should be `equal' (timestamps +aside) that aren't also `eq'.") + +(cl-defmethod erc-networks--id-equal-p ((_ null) (_ erc-networks--id)) nil) +(cl-defmethod erc-networks--id-equal-p ((_ erc-networks--id) (_ null)) nil) (cl-defmethod erc-networks--id-equal-p ((self erc-networks--id) (other erc-networks--id)) @@ -1381,7 +1373,8 @@ erc-networks--update-server-identity (let* ((identity erc-networks--id) (buffer (current-buffer)) (f (lambda () - (unless (or (eq (current-buffer) buffer) + (unless (or (not erc-networks--id) + (eq (current-buffer) buffer) (eq erc-networks--id identity)) (if (erc-networks--id-equal-p identity erc-networks--id) (throw 'buffer erc-networks--id) @@ -1400,8 +1393,8 @@ erc-networks--init-identity "Update identity with real network name." ;; Initialize identity for real now that we know the network (cl-assert erc-network) - (unless (erc-networks--id-symbol erc-networks--id) ; unless just reconnected - (erc-networks--id-on-connect erc-networks--id)) + (unless erc-networks--id + (setq erc-networks--id (erc-networks--id-create nil))) ;; Find duplicate identities or other conflicting ones and act ;; accordingly. (erc-networks--update-server-identity) diff --git a/lisp/erc/erc-sasl.el b/lisp/erc/erc-sasl.el index f36a305247..ac2646051c 100644 --- a/lisp/erc/erc-sasl.el +++ b/lisp/erc/erc-sasl.el @@ -19,8 +19,8 @@ ;;; Commentary: -;; This "non-IRCv3" implementation resembles many others that have -;; surfaced over the years, the first possibly being: +;; This "non-IRCv3" implementation resembles others that have surfaced +;; over the years, the first possibly being from Joseph Gay: ;; ;; https://lists.gnu.org/archive/html/erc-discuss/2012-02/msg00001.html ;; @@ -30,29 +30,35 @@ ;; ;; - Find a way to obfuscate the password in memory (via something ;; like `auth-source--obfuscate'); it's currently visible in -;; backtraces. +;; backtraces and bug reports. ;; ;; - Implement a proxy mechanism that chooses the strongest available ;; mechanism for you. Requires CAP 3.2 (see bug#49860). +;; +;; - Integrate with whatever solution ERC eventually settles on to +;; handle user options for different network contexts. At the +;; moment, this does its own thing for stashing and restoring +;; session options, but ERC should make abstractions available for +;; all local modules to use, possibly based on connection-local +;; variables. ;;; Code: (require 'erc) (require 'rx) (require 'sasl) (require 'sasl-scram-rfc) -(require 'sasl-scram-sha256 nil t) +(require 'sasl-scram-sha256 nil t) ; not present in Emacs 27 (defgroup erc-sasl nil "SASL for ERC." :group 'erc - :package-version '(ERC . "5.4")) ; FIXME increment on next release + :package-version '(ERC . "5.4.1")) ; FIXME increment on next release -(defcustom erc-sasl-mechanism nil +(defcustom erc-sasl-mechanism 'plain "SASL mechanism to connect with. Note that any value other than nil or `external' likely requires `erc-sasl-user' and `erc-sasl-password'." - :type '(choice (const nil) - (const plain) + :type '(choice (const plain) (const external) (const scram-sha-1) (const scram-sha-256) @@ -68,17 +74,18 @@ erc-sasl-user (defcustom erc-sasl-password nil "Optional account password to send when authenticating. -When the value is a string, ERC uses it unconditionally for most -mechanisms (see below). As a special case, when the value is a -non-nil symbol, ERC uses it as the value of the `:host' field in -an auth-source query, provided `erc-sasl-auth-source-function' is -set to a function. When nil, ERC will try a non-nil \"session -password\", likely one given as the `:password' argument to -`erc-tls'. As a last resort, ERC will prompt the user for input. +When the value is a string, ERC will use it unconditionally for +most mechanisms. Otherwise, when `erc-sasl-auth-source-function' +is a function, ERC will attempt an auth-source query, possibly +using a non-nil symbol for the suggested `:host' parameter if set +as this option's value or passed as an `:id' to `erc-tls'. +Failing that, ERC will try a non-nil \"session password\" if one +is on file, typically from a `:password' argument supplied to +`erc-tls'. As a last resort, ERC will prompt for input. Note that when `erc-sasl-mechanism' is set to `ecdsa-nist256p-challenge', this option should hold the file name -of the key, which is typically in PEM format." +of the key." :type '(choice (const nil) string symbol)) (defcustom erc-sasl-auth-source-function nil @@ -91,7 +98,7 @@ erc-sasl-auth-source-function move on to the next approach, as described in the doc string for the option `erc-sasl-password'. See info node `(erc) Connecting' for details on ERC's auth-source integration." - :type '(choice (const erc-auth-source-search) + :type '(choice (function-item erc-auth-source-search) (const nil) function)) @@ -103,6 +110,13 @@ erc-sasl-authzid ;; Analogous to what erc-backend does to persist opening params. (defvar-local erc-sasl--options nil) +;; In the future, ERC will hopefully use connection-local variables to +;; handle such bookkeeping transparently. +(defvar erc-sasl--session-options nil + "An alist associating network-IDs to `erc-sasl--options'. +This is for persisting user options captured at entry-point +invocation throughout an Emacs session.") + ;; Session-local (server buffer) SASL subproto state (defvar-local erc-sasl--state nil) @@ -263,13 +277,26 @@ erc-sasl--create-client (sasl-client-set-property client 'ecdsa-keyfile keyfile) client))) -;; This stands alone because it's also used by bug#49860 +;; This stands alone because it's also used by bug#49860. (defun erc-sasl--init () - (setq erc-sasl--state (make-erc-sasl--state) - erc-sasl--options `((user . ,erc-sasl-user) - (password . ,erc-sasl-password) - (mechanism . ,erc-sasl-mechanism) - (authzid . ,erc-sasl-authzid)))) + ;; When reconnecting, try to recover stashed parameters. + (let ((existing (assoc erc-networks--id erc-sasl--session-options + #'erc-networks--id-equal-p))) + ;; This likely only runs when `erc' was called with an :id keyword. + (when (and existing (not erc--server-reconnecting)) + (setq erc-sasl--session-options (delq existing erc-sasl--session-options) + existing nil)) + (setq erc-sasl--state (make-erc-sasl--state) + erc-sasl--options (or (cdr existing) + `((user . ,erc-sasl-user) + (password . ,erc-sasl-password) + (mechanism . ,erc-sasl-mechanism) + (authzid . ,erc-sasl-authzid)))))) + +(defun erc-sasl--on-connection-established (&rest _) + (setf (alist-get erc-networks--id erc-sasl--session-options nil nil + #'erc-networks--id-equal-p) + erc-sasl--options)) (defun erc-sasl--mechanism-offered-p (offered) "Return non-nil when OFFERED appears among a list of mechanisms." @@ -359,6 +386,7 @@ erc-sasl--destroy (when erc-sasl-mode (unless erc-server-connected (erc-server-send "CAP END"))) + (add-hook 'erc-after-connect #'erc-sasl--on-connection-established 0 t) (erc-handle-unknown-server-response proc parsed)) (define-erc-response-handler (907) diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el index 2869383960..a703f903ec 100644 --- a/lisp/erc/erc.el +++ b/lisp/erc/erc.el @@ -1780,31 +1780,6 @@ erc-default-nicks (defvar-local erc-nick-change-attempt-count 0 "Used to keep track of how many times an attempt at changing nick is made.") -(defconst erc--features-to-modules - '((erc-pcomplete completion pcomplete) - (erc-capab capab-identify) - (erc-join autojoin) - (erc-page page ctcp-page) - (erc-sound sound ctcp-sound) - (erc-stamp stamp timestamp) - (erc-services services nickserv)) - "Migration alist mapping a library feature to module names. -Keys need not be unique: a library may define more than one -module.") - -(defconst erc--modules-to-features - (cl-loop for (feature . names) in erc--features-to-modules - append (mapcar (lambda (name) (cons name feature)) names)) - "Migration alist mapping a module's name to library feature.") - -(defconst erc--module-name-migrations - (let (pairs) - (pcase-dolist (`(,_ ,canonical . ,rest) erc--features-to-modules) - (dolist (obsolete rest) - (push (cons obsolete canonical) pairs))) - pairs) - "Association list of obsolete module names to canonical names.") - (defun erc-migrate-modules (mods) "Migrate old names of ERC modules to new ones." ;; modify `transforms' to specify what needs to be changed @@ -1888,23 +1863,25 @@ erc-modules (repeat :tag "Others" :inline t symbol)) :group 'erc) -(defun erc-update-modules () +(defun erc-update-modules (&optional defer-locals) "Enable global minor mode for all global modules in `erc-modules'. -Return minor-mode commands for all local modules, possibly for -deferred invocation, as done by `erc-open' whenever a new ERC -buffer is created. Local modules were introduced in ERC 5.6." - (let (local-modules) - (dolist (mod erc-modules) - (require (or (alist-get mod erc--modules-to-features) - (intern (concat "erc-" (symbol-name mod)))) +With DEFER-LOCALS, return minor-mode commands for all local +modules, possibly for deferred invocation, as done by `erc-open' +whenever a new ERC buffer is created. Local modules were +introduced in ERC 5.5." + (let ((local-modes + (when (and defer-locals (derived-mode-p 'erc-mode)) + (erc-compat--local-minor-modes)))) + (dolist (module erc-modules (and defer-locals local-modes)) + (require (or (alist-get module erc--modules-to-features) + (intern (concat "erc-" (symbol-name module)))) nil 'noerror) ; some modules don't have a corresponding feature - (let ((sym (intern-soft (concat "erc-" (symbol-name mod) "-mode")))) - (unless (and sym (fboundp sym)) - (error "`%s' is not a known ERC module" mod)) - (if (custom-variable-p sym) - (funcall sym 1) - (push sym local-modules)))) - local-modules)) + (let ((mode (intern-soft (concat "erc-" (symbol-name module) "-mode")))) + (unless (and mode (fboundp mode)) + (error "`%s' is not a known ERC module" module)) + (if (and defer-locals (not (custom-variable-p mode))) + (cl-pushnew mode local-modes) + (funcall mode 1)))))) (defun erc-setup-buffer (buffer) "Consults `erc-join-buffer' to find out how to display `BUFFER'." @@ -1966,15 +1943,17 @@ erc-open (continued-session (and erc--server-reconnecting (with-suppressed-warnings ((obsolete erc-reuse-buffers)) - erc-reuse-buffers)))) + erc-reuse-buffers) + erc-networks--id))) (when connect (run-hook-with-args 'erc-before-connect server port nick)) (set-buffer buffer) (setq old-point (point)) - (setq delayed-modules (erc-update-modules)) + (setq delayed-modules (erc-update-modules 'defer-locals)) (delay-mode-hooks (erc-mode)) - (setq erc-server-reconnect-count old-recon-count) + (setq erc-server-reconnect-count old-recon-count + erc--server-reconnecting continued-session) (when (setq erc-server-connected (not connect)) (setq erc-server-announced-name @@ -2030,10 +2009,11 @@ erc-open (setq erc-default-nicks (if (consp erc-nick) erc-nick (list erc-nick))) ;; client certificate (only useful if connecting over TLS) (setq erc-session-client-certificate client-certificate) - (setq erc-networks--id (if connect - (erc-networks--id-create id) - (buffer-local-value 'erc-networks--id - old-buffer))) + (setq erc-networks--id + (if connect + (or erc--server-reconnecting + (and id (erc-networks--id-create id))) + (buffer-local-value 'erc-networks--id old-buffer))) ;; debug output buffer (setq erc-dbuf (when erc-log-p @@ -3197,7 +3177,8 @@ erc-auth-source-join-function function)) (defun erc--auth-source-determine-params-defaults () - (let* ((net (and-let* ((esid (erc-networks--id-symbol erc-networks--id)) + (let* ((net (and-let* ((erc-networks--id) + (esid (erc-networks--id-symbol erc-networks--id)) ((symbol-name esid))))) (localp (and erc--target (erc--target-channel-local-p erc--target))) (hosts (if localp diff --git a/test/lisp/erc/erc-sasl-tests.el b/test/lisp/erc/erc-sasl-tests.el index 112303baf5..81db9ad948 100644 --- a/test/lisp/erc/erc-sasl-tests.el +++ b/test/lisp/erc/erc-sasl-tests.el @@ -1,6 +1,6 @@ ;;; erc-sasl-tests.el --- Tests for erc-sasl. -*- lexical-binding:t -*- -;; Copyright (C) 2020-2022 Free Software Foundation, Inc. +;; Copyright (C) 2022 Free Software Foundation, Inc. ;; ;; This file is part of GNU Emacs. ;; @@ -276,6 +276,10 @@ erc-sasl-tests-ecdsa-key-file ") (ert-deftest erc-sasl-create-client-ecdsa () + :tags '(:unstable) + ;; This is currently useless because it just roundtrips shelling out + ;; to pkeyutl. + (ert-skip "Placeholder") (unless (executable-find "openssl") (ert-skip "System lacks openssl")) (ert-with-temp-file keyfile @@ -295,8 +299,21 @@ erc-sasl-create-client-ecdsa "\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37"))) (sasl-step-set-data step resp) (setq step (sasl-next-step client step)) - ;; FIXME this is dumb - (should (<= 68 (length (sasl-step-data step)) 72)))) + (ert-with-temp-file sigfile + :prefix "ecdsa_sig" + :suffix ".sig" + :text (sasl-step-data step) + (with-temp-buffer + (set-buffer-multibyte nil) + (insert resp) + (let ((ec (call-process-region + (point-min) (point-max) + "openssl" 'delete t nil "pkeyutl" + "-inkey" keyfile "-sigfile" sigfile + "-verify"))) + (unless (zerop ec) + (message "%s" (buffer-string))) + (should (zerop ec))))))) (should-not (sasl-next-step client step))))) ;;; erc-sasl-tests.el ends here diff --git a/test/lisp/erc/erc-scenarios-sasl.el b/test/lisp/erc/erc-scenarios-sasl.el index 3ff7cc805d..7970e65ec2 100644 --- a/test/lisp/erc/erc-scenarios-sasl.el +++ b/test/lisp/erc/erc-scenarios-sasl.el @@ -41,6 +41,7 @@ erc-scenarios-sasl--plain (erc-modules (cons 'sasl erc-modules)) (erc-sasl-mechanism 'plain) (erc-sasl-password "password123") + (erc-sasl--session-options nil) (inhibit-message noninteractive) (expect (erc-d-t-make-expecter))) @@ -60,6 +61,49 @@ erc-scenarios-sasl--plain ;; Regression "\0\0\0\0 ..." caused by (fillarray passphrase 0) (should (string= erc-sasl-password "password123")))))) +;; This is meant to assert `erc-update-modules' and local-module +;; behavior generally. It only exists here for convenience because as +;; of ERC 5.5, `sasl' is the only local module. +(ert-deftest erc-scenarios-sasl--local-modules-reconnect () + :tags '(:expensive-test) + (erc-scenarios-common-with-cleanup + ((erc-scenarios-common-dialog "sasl") + (erc-server-flood-penalty 0.1) + (dumb-server (erc-d-run "localhost" t 'plain 'plain)) + (port (process-contact dumb-server :service)) + (erc-sasl--session-options nil) + (inhibit-message noninteractive) + (expect (erc-d-t-make-expecter))) + + (ert-info ("Connect with options let-bound") + (with-current-buffer + ;; This won't work unless the library is already loaded + (let ((erc-modules (cons 'sasl erc-modules)) + (erc-sasl-mechanism 'plain) + (erc-sasl-password "password123")) + (erc :server "127.0.0.1" + :port port + :nick "tester" + :user "tester" + :full-name "tester")) + (should (string= (buffer-name) (format "127.0.0.1:%d" port))))) + + (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "ExampleOrg")) + (ert-info ("First connection succeeds") + (funcall expect 10 "This server is in debug mode") + (erc-cmd-QUIT "") + (funcall expect 10 "finished")) + + (should-not erc-sasl-password) ; obviously + (should-not (memq 'sasl erc-modules)) + + (erc-d-t-wait-for 10 (not (erc-server-process-alive))) + (erc-cmd-RECONNECT) + (ert-info ("Second connection succeeds") + (funcall expect 10 "This server is in debug mode") + (erc-cmd-QUIT "") + (funcall expect 10 "finished"))))) + (ert-deftest erc-scenarios-sasl--external () :tags '(:expensive-test) (erc-scenarios-common-with-cleanup @@ -70,6 +114,7 @@ erc-scenarios-sasl--external (port (process-contact dumb-server :service)) (erc-modules (cons 'sasl erc-modules)) (erc-sasl-mechanism 'external) + (erc-sasl--session-options nil) (inhibit-message noninteractive) (expect (erc-d-t-make-expecter))) @@ -99,6 +144,7 @@ erc-scenarios-sasl--plain-fail (erc-modules (cons 'sasl erc-modules)) (erc-sasl-password "wrong") (erc-sasl-mechanism 'plain) + (erc-sasl--session-options nil) (inhibit-message noninteractive) (expect (erc-d-t-make-expecter)) (buf nil)) @@ -128,6 +174,7 @@ erc-scenarios--common--sasl (erc-modules (cons 'sasl erc-modules)) (erc-sasl-password "sesame") (erc-sasl-mechanism mech) + (erc-sasl--session-options nil) (mock-rvs (list "c5RqLCZy0L4fGkKAZ0hujFBs" "")) (sasl-unique-id-function (lambda () (pop mock-rvs))) (inhibit-message noninteractive) diff --git a/test/lisp/erc/erc-tests.el b/test/lisp/erc/erc-tests.el index 4646c35e25..91815b8fae 100644 --- a/test/lisp/erc/erc-tests.el +++ b/test/lisp/erc/erc-tests.el @@ -960,44 +960,60 @@ erc-migrate-modules (should (equal (erc-migrate-modules erc-modules) erc-modules))) (ert-deftest erc-update-modules () - (let* (calls - (erc-modules '(fake-foo fake-bar))) + (let (calls + erc-modules + erc-kill-channel-hook erc-kill-server-hook erc-kill-buffer-hook) (cl-letf (((symbol-function 'require) (lambda (s &rest _) (push s calls))) - ((symbol-function 'erc-fake-foo-mode) - (lambda (n) (push (cons 'fake-foo n) calls))) - ;; Here, foo is a global module (minor mode) - ((get 'erc-fake-foo-mode 'standard-value) #'ignore) + + ;; Local modules ((symbol-function 'erc-fake-bar-mode) (lambda (n) (push (cons 'fake-bar n) calls))) + + ;; Global modules + ((symbol-function 'erc-fake-foo-mode) + (lambda (n) (push (cons 'fake-foo n) calls))) + ((get 'erc-fake-foo-mode 'standard-value) 'ignore) ((symbol-function 'erc-autojoin-mode) (lambda (n) (push (cons 'autojoin n) calls))) - ((get 'erc-autojoin-mode 'standard-value) #'ignore) + ((get 'erc-autojoin-mode 'standard-value) 'ignore) ((symbol-function 'erc-networks-mode) (lambda (n) (push (cons 'networks n) calls))) + ((get 'erc-networks-mode 'standard-value) 'ignore) ((symbol-function 'erc-completion-mode) (lambda (n) (push (cons 'completion n) calls))) - ((get 'erc-completion-mode 'standard-value) #'ignore)) - - (ert-info ("Locals") - (should (equal (erc-update-modules) - '(erc-fake-bar-mode))) - ;; Bar still required - (should (equal (nreverse calls) '(erc-fake-foo - (fake-foo . 1) - erc-fake-bar))) + ((get 'erc-completion-mode 'standard-value) 'ignore)) + + (ert-info ("Local modules") + (setq erc-modules '(fake-foo fake-bar)) + (should (equal (erc-update-modules t) '(erc-fake-bar-mode))) + ;; Bar the feature is still required but the mode is not activated + (should (equal (nreverse calls) + '(erc-fake-foo (fake-foo . 1) erc-fake-bar))) (setq calls nil)) (ert-info ("Module name overrides") (setq erc-modules '(completion autojoin networks)) - (should-not (erc-update-modules)) ; no locals - (should (equal (nreverse calls) - '(erc-pcomplete - (completion . 1) - erc-join - (autojoin . 1) - erc-networks - (networks . 1)))) - (setq calls nil))))) + (should-not (erc-update-modules t)) ; no locals + (should (equal (nreverse calls) '( erc-pcomplete (completion . 1) + erc-join (autojoin . 1) + erc-networks (networks . 1)))) + (setq calls nil)) + + (ert-info ("Reenabling of local minor modes by `erc-open'") + (with-temp-buffer + (erc-mode) + (setq erc-modules '(completion autojoin networks)) + (if (< 27 emacs-major-version) + (let ((local-minor-modes '(font-lock-mode erc-fake-bar-mode))) + (should (equal (erc-update-modules t) '(erc-fake-bar-mode)))) + (cl-letf (((symbol-function 'buffer-local-variables) + (lambda (&rest _) '((font-lock-mode) + (erc-fake-bar-mode))))) + (should (equal (erc-update-modules t) '(erc-fake-bar-mode))))) + (should (equal (nreverse calls) + '( erc-pcomplete (completion . 1) + erc-join (autojoin . 1) + erc-networks (networks . 1))))))))) ;;; erc-tests.el ends here diff --git a/test/lisp/erc/resources/sasl/plain.eld b/test/lisp/erc/resources/sasl/plain.eld index 9c6ce3feeb..1341cd78e5 100644 --- a/test/lisp/erc/resources/sasl/plain.eld +++ b/test/lisp/erc/resources/sasl/plain.eld @@ -33,3 +33,7 @@ ((mode-user 1.2 "MODE tester +i") (0.0 ":irc.example.org 221 tester +Zi") (0.0 ":irc.example.org NOTICE tester :This server is in debug mode and is logging all user I/O. If you do not wish for everything you send to be readable by the server owner(s), please disconnect.")) + +((quit 5 "QUIT :\2ERC\2") + (0 ":tester!~u@yuvqisyu7m7qs.irc QUIT :Quit")) +((drop 1 DROP)) -- 2.38.1