From a06e72aca3f14d903f5716f844067d3a224919cd Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Fri, 18 Nov 2022 00:11:15 -0800 Subject: [PATCH 0/6] *** NOT A PATCH *** *** BLURB HERE *** F. Jason Park (6): 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 Accept functions in place of passwords in ERC doc/misc/erc.texi | 161 ++++++- etc/ERC-NEWS | 15 +- lisp/erc/erc-backend.el | 18 +- 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 | 429 ++++++++++++++++++ lisp/erc/erc-services.el | 5 +- lisp/erc/erc.el | 125 +++-- lisp/net/sasl-scram-rfc.el | 21 +- test/lisp/erc/erc-sasl-tests.el | 344 ++++++++++++++ test/lisp/erc/erc-scenarios-sasl.el | 202 +++++++++ test/lisp/erc/erc-services-tests.el | 16 +- test/lisp/erc/erc-tests.el | 80 ++++ 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 ++ 20 files changed, 1712 insertions(+), 98 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 d248051871..790db1135e 100644 --- a/doc/misc/erc.texi +++ b/doc/misc/erc.texi @@ -611,6 +611,7 @@ Connecting parameters, and some, like @code{client-certificate}, will just be @code{nil}. +@anchor{ERC client-certificate} To use a certificate with @code{erc-tls}, specify the optional @var{client-certificate} keyword argument, whose value should be as described in the documentation of @code{open-network-stream}: if @@ -745,7 +746,10 @@ Connecting You can manually set another nickname with the /NICK command. @end defopt +@anchor{ERC username} @subheading User +@cindex user + @defun erc-compute-user &optional user Determine a suitable value to send as the first argument of the opening @samp{USER} IRC command by consulting the following sources: @@ -937,21 +941,11 @@ SASL @section Authenticating via SASL @cindex SASL -@strong{Warning:} ERC's SASL offering is currently limited by a lack -of support for proper IRCv3 capability negotiation. In most cases, -this shouldn't affect your ability to authenticate. If you run into -trouble, please contact us (@pxref{Getting Help and Reporting Bugs}). - Regardless of the mechanism or the network, you'll likely have to be registered before first use. Please refer to the network's own instructions for details. If you're new to IRC and using a bouncer, -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, 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 +know that you probably won't be needing SASL for the client-to-bouncer +connection. 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. @@ -962,35 +956,35 @@ SASL @indentedblock Here, ``password'' refers to your account password, which is usually -your @samp{NickServ} password. This often differs from any connection -(server) password given to @code{erc-tls} via its @code{:password} -parameter. To make this work, customize both @code{erc-sasl-user} and -@code{erc-sasl-password} or bind them when invoking @code{erc-tls}. +your @samp{NickServ} password. To make this work, customize +@code{erc-sasl-user} and @code{erc-sasl-password} or specify the +@code{:user} and @code{:password} keyword arguments when invoking +@code{erc-tls}. Note that @code{:user} cannot be given interactively. @end indentedblock @var{external} (via Client TLS Certificate): @indentedblock -You'll want to specify the @code{:client-certificate} param when -opening a new connection, which is typically done by calling -@code{emacs-tls}. But before that, ensure you've registered your -fingerprint with the network. The fingerprint is usually a SHA1 or -SHA256 digest in either "normalized" or "openssl" forms. The first is -lowercase without delims (@samp{deadbeef}) and the second uppercase -with colon seps (@samp{DE:AD:BE:EF}). +This works in conjunction with the @code{:client-certificate} keyword +offered by @code{erc-tls}. Just ensure you've registered your +fingerprint with the network beforehand. The fingerprint is usually a +SHA1 or SHA256 digest in either "normalized" or "openssl" forms. The +first is lowercase without delims (@samp{deadbeef}) and the second +uppercase with colon seps (@samp{DE:AD:BE:EF}). These days, there's +usually a @samp{CERT ADD} command offered by NickServ that can +register you automatically if you issue it while connected with a +client cert. (@pxref{ERC client-certificate}). Additional considerations: @enumerate @item -There's no reason to send your password after registering. -@item Most IRCds will allow you to authenticate with a client cert but without the hassle of SASL (meaning you may not need this module). @item Technically, @var{EXTERNAL} merely indicates that an out-of-band mode of authentication is in effect (being deferred to), so depending on -the specific application or service, there's an off chance client -certs aren't involved. +the specific application or service, there's a remote chance your +server has something else in mind. @end enumerate @end indentedblock @@ -1015,33 +1009,33 @@ SASL @end defopt @defopt erc-sasl-user -This should be your network account name, typically the same one -registered with nickname services. Specify this when your -@samp{NickServ} account name differs from the nick you're connecting -with. +This should be your network account username, typically the same one +registered with nickname services. Specify this when your NickServ +login differs from the @code{:user} you're connecting with. +(@pxref{ERC username}) @end defopt @defopt erc-sasl-password -For ``password-based'' mechanisms, ERC sends any nonempty string as -the authentication password. - -If you instead give a non-@code{nil} symbol, like @samp{Libera.Chat}, -ERC will use it for the @code{:host} field in an auth-source query. +As noted elsewhere, the @code{:password} parameter for @code{erc-tls} +was orignally intended for traditional ``server passwords,'' but these +aren't really used any more. As such, this option defaults to +borrowing that parameter for its own uses, thus allowing you to call +@code{erc-tls} with @code{:password} set to your NickServ password. + +You can also set this to a nonemtpy string, and ERC will send that +when needed, no questions asked. If you instead give a non-@code{nil} +symbol (other than @code{:password}), like @samp{Libera.Chat}, ERC +will use it for the @code{:host} field in an auth-source query. Actually, the same goes for when this option is @code{nil} but an explicit session ID is already on file (@pxref{Network Identifier}). -For all such queries, ERC specifies the value of @code{erc-sasl-user} -for the @code{:user} (@code{:login}) param. Keep in mind that none of -this matters unless @code{erc-sasl-auth-source-function} holds a -function (it's @code{nil} by default). - -Otherwise, if you set this option to @code{nil} (or the empty string) -or if an auth-source lookup has failed, ERC will try a non-@code{nil} -``server password,'' likely whatever you gave as the @var{password} -argument to @code{erc-tls}. This fallback behavior may change, -however, so please don't rely on it. As a last resort, ERC will -prompt you for input. - -Also, if your mechanism is @code{ecdsa-nist256p-challenge}, this +For all such queries, ERC specifies the resolved value of +@code{erc-sasl-user} for the @code{:user} (@code{:login}) param. Keep +in mind that none of this matters unless +@code{erc-sasl-auth-source-function} holds a function, and it's +@code{nil} by default. As a last resort, ERC will prompt you for +input. + +Lastly, if your mechanism is @code{ecdsa-nist256p-challenge}, this option should instead hold the file name of your key. @end defopt @@ -1060,6 +1054,25 @@ SASL leave this set to @code{nil}. @end defopt +@subheading Troubleshooting + +@strong{Warning:} ERC's SASL offering is currently limited by a lack +of support for proper IRCv3 capability negotiation. In most cases, +this shouldn't affect your ability to authenticate. + +If you're struggling, remember that your SASL password is almost +always your NickServ password. When in doubt, try restoring all SASL +options to their defaults and calling @code{erc-tls} with @code{:user} +set to your NickServ account name and @code{:password} to your +NickServ password. If you're still having trouble, please contact us +(@pxref{Getting Help and Reporting Bugs}). + +And if, for whatever reason, you do find yourself trying out +non-default SASL settings, keep in mind that every change requires a +fresh session, so you'll want to call @code{erc-tls} from scratch +again rather than rely on @samp{/reconnect} or the auto-reconnect +facility. In fact, it's best to temporarily set +@code{erc-server-auto-reconnect} to @code{nil} while experimenting. @node Sample Configuration @section Sample Configuration diff --git a/etc/ERC-NEWS b/etc/ERC-NEWS index 37b9928cf8..829ef25a47 100644 --- a/etc/ERC-NEWS +++ b/etc/ERC-NEWS @@ -103,18 +103,13 @@ messages during periods of heavy traffic no longer disappear. Although rare, server passwords containing white space are now handled correctly. -** Local modules and ERC-mode hooks are more useful. -The 'local-p' parameter of 'define-erc-module' now affects more than -the scope of a module's minor-mode. This currently has little direct -impact on the user experience, but third-party packages may wish to -take note. - -More importantly, the function 'erc-update-modules' now supports an -optional argument to defer enabling of local modules and instead -return their mode commands. 'erc-open' leverages this to delay their -activation, as well as that of all 'erc-mode-hook' members, until most -local session variables have been initialized (minus those "server"- -and process-focused ones in erc-backend). +** ERC-mode hooks are more useful. +The function 'erc-update-modules' now supports an optional argument to +defer enabling of local modules and instead return their mode +commands. 'erc-open' relies on this to delay their activation, as +well as that of all 'erc-mode-hook' members, until most local session +variables have been initialized (minus those "server"- and +process-focused ones in erc-backend). ** Miscellaneous behavioral changes in the library API. A number of core macros and other definitions have been moved to a new diff --git a/lisp/erc/erc-sasl.el b/lisp/erc/erc-sasl.el index d8ef600351..aabb6c8a51 100644 --- a/lisp/erc/erc-sasl.el +++ b/lisp/erc/erc-sasl.el @@ -30,7 +30,7 @@ ;; ;; - Find a way to obfuscate the password in memory (via something ;; like `auth-source--obfuscate'); it's currently visible in -;; backtraces and bug reports. +;; backtraces. ;; ;; - Implement a proxy mechanism that chooses the strongest available ;; mechanism for you. Requires CAP 3.2 (see bug#49860). @@ -65,39 +65,44 @@ erc-sasl-mechanism (const scram-sha-512) (const ecdsa-nist256p-challenge))) -(defcustom erc-sasl-user nil - "Optional account username to send when authenticating. -This is also referred to as the authentication identity, or -\"authcid\". When nil, applicable mechanisms will use the -session's current nick." - :type '(choice string (const nil))) - -(defcustom erc-sasl-password nil +(defcustom erc-sasl-user :user + "Account username to send when authenticating. +This is also referred to as the authentication identity or +\"authcid\". A value of `:user' or `:nick' indicates that the +corresponding connection parameter on file should be used. These +are most often derived from arguments provided to the `erc' and +`erc-tls' entry points. In the case of `:nick', a downcased +version is used." + :type '(choice string (const :user) (const :nick))) + +(defcustom erc-sasl-password :password "Optional account password to send when authenticating. 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." - :type '(choice (const nil) string symbol)) +most mechanisms. Likewise with `:password', except ERC will +instead use the \"session password\" on file, which often +originates from the entry-point commands `erc' or `erc-tls'. +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 prompt for input. + +Note that, with `:password', ERC will forgo sending a traditional +server password via the IRC \"PASS\" command. Also, when +`erc-sasl-mechanism' is set to `ecdsa-nist256p-challenge', this +option should hold the file name of the key." + :type '(choice (const nil) (const :password) string symbol)) (defcustom erc-sasl-auth-source-function nil "Function to query auth-source for an SASL password. Called with keyword params known to `auth-source-search', which -may include a non-nil `erc-sasl-user' for the `:user' field -and a non-nil `erc-sasl-password' for the `:host' field, when -the latter option is a symbol instead of a string. In return, -ERC expects a string to send as the SASL password, or nil, to -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." +includes `erc-sasl-user' for the `:user' field and +`erc-sasl-password' for the `:host' field, when the latter option +is a non-nil, non-keyword symbol. In return, ERC expects a +string to send as the SASL password, or nil, to 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 (function-item erc-auth-source-search) (const nil) function)) @@ -110,13 +115,6 @@ 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) @@ -126,23 +124,27 @@ erc-sasl--state (step nil :type vector) (pending nil :type string)) +(defun erc-sasl--get-user () + (pcase (alist-get 'user erc-sasl--options) + (:user erc-session-username) + (:nick (erc-downcase (erc-current-nick))) + (v v))) + (defun erc-sasl--read-password (prompt) "Return configured option or server password. PROMPT is passed to `read-passwd' if necessary." - (let* ((pass (alist-get 'password erc-sasl--options)) - (found - (or (and (stringp pass) (not (string-empty-p pass)) pass) - (and erc-sasl-auth-source-function - (let ((user (alist-get 'user erc-sasl--options)) - (host (or pass - (erc-networks--id-given erc-networks--id)))) - (apply erc-sasl-auth-source-function - `(,@(and user (list :user user)) - ,@(and host (list :host (symbol-name host))))))) - erc-session-password))) - (if found - (copy-sequence (erc--unfun found)) - (read-passwd prompt)))) + (if-let + ((found (pcase (alist-get 'password erc-sasl--options) + (:password erc-session-password) + ((and (pred stringp) v) (unless (string-empty-p v) v)) + ((and (guard erc-sasl-auth-source-function) + v (let host + (or v (erc-networks--id-given erc-networks--id)))) + (apply erc-sasl-auth-source-function + :user (erc-sasl--get-user) + (and host (list :host (symbol-name host)))))))) + (copy-sequence (erc--unfun found)) + (read-passwd prompt))) (defun erc-sasl--plain-response (client steps) (let ((sasl-read-passphrase #'erc-sasl--read-password)) @@ -228,22 +230,20 @@ erc-sasl--create-client (when feature (setf (alist-get name sasl-mechanism-alist nil nil #'equal) `(,feature)) (cl-pushnew name sasl-mechanisms :test #'equal) - (setq client (sasl-make-client (sasl-find-mechanism `(,name)) - (or (alist-get 'user erc-sasl--options) - (erc-downcase (erc-current-nick))) + (setq client (sasl-make-client (sasl-find-mechanism (list name)) + (erc-sasl--get-user) "N/A" "N/A")) (sasl-client-set-property client 'authenticator-name (alist-get 'authzid erc-sasl--options)) client))) -(cl-defmethod erc-sasl--create-client ((_m (eql plain))) +(cl-defmethod erc-sasl--create-client ((_ (eql plain))) "Create and return a new PLAIN client object." ;; https://tools.ietf.org/html/rfc4616#section-2. (let* ((sans (remq (assoc "PLAIN" sasl-mechanism-alist) sasl-mechanism-alist)) (sasl-mechanism-alist (cons '("PLAIN" erc-sasl-plain) sans)) - (authc (or (alist-get 'user erc-sasl--options) - (erc-downcase (erc-current-nick)))) + (authc (erc-sasl--get-user)) (port (if (numberp erc-session-port) (number-to-string erc-session-port) "0")) @@ -255,58 +255,51 @@ erc-sasl--create-client (alist-get 'authzid erc-sasl--options)) client)) -(cl-defmethod erc-sasl--create-client ((m (eql scram-sha-256))) +(cl-defmethod erc-sasl--create-client ((_ (eql scram-sha-256))) "Create and return a new SCRAM-SHA-256 client." - (unless (featurep 'sasl-scram-sha256) - (user-error "SASL mechanism %s unsupported" m)) - (cl-call-next-method)) + (when (featurep 'sasl-scram-sha256) + (cl-call-next-method))) -(cl-defmethod erc-sasl--create-client ((m (eql scram-sha-512))) +(cl-defmethod erc-sasl--create-client ((_ (eql scram-sha-512))) "Create and return a new SCRAM-SHA-512 client." - (unless (featurep 'sasl-scram-sha256) - (user-error "SASL mechanism %s unsupported" m)) - (cl-call-next-method)) + (when (featurep 'sasl-scram-sha256) + (cl-call-next-method))) (cl-defmethod erc-sasl--create-client ((_ (eql ecdsa-nist256p-challenge))) "Create and return a new ECDSA-NIST256P-CHALLENGE client." - (unless (executable-find "openssl") - (user-error "Could not find openssl command-line utility")) (let ((keyfile (cdr (assq 'password erc-sasl--options)))) - (unless (and keyfile (file-exists-p keyfile)) - (user-error "`erc-sasl-password' does not point to ECDSA keyfile")) - (let ((client (cl-call-next-method))) - (sasl-client-set-property client 'ecdsa-keyfile keyfile) - client))) + ;; Better to signal usage errors now than inside a process filter. + (cond ((or (not (stringp keyfile)) (not (file-readable-p keyfile))) + (erc-display-error-notice + nil "`erc-sasl-password' not accessible as a file") + nil) + ((not (executable-find "openssl")) + (erc-display-error-notice nil "Could not find openssl program") + nil) + (t + (let ((client (cl-call-next-method))) + (sasl-client-set-property client 'ecdsa-keyfile keyfile) + client))))) ;; This stands alone because it's also used by bug#49860. (defun erc-sasl--init () - ;; 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 - ;; - erc-sasl--options nil)) + (setq erc-sasl--state (make-erc-sasl--state)) + ;; If the previous attempt failed during registration, this may be + ;; non-nil and contain erroneous values, but how can we detect that? + ;; What if the server dropped the connection for some other reason? + (setq erc-sasl--options + (or (and (consp erc--server-reconnecting) + (alist-get 'erc-sasl--options erc--server-reconnecting)) + `((user . ,erc-sasl-user) + (password . ,erc-sasl-password) + (mechanism . ,erc-sasl-mechanism) + (authzid . ,erc-sasl-authzid))))) (defun erc-sasl--mechanism-offered-p (offered) "Return non-nil when OFFERED appears among a list of mechanisms." (string-match-p (rx-to-string `(: (| bot ",") - ,(symbol-name - (alist-get 'mechanism erc-sasl--options)) + ,(symbol-name (alist-get 'mechanism erc-sasl--options)) (| eot ","))) (downcase offered))) @@ -347,7 +340,7 @@ erc-sasl--authenticate-handler (s905 . "ERR SASLTOOLONG (credentials too long) %s") (s906 . "ERR_SASLABORTED (authentication aborted) %s") (s907 . "ERR_SASLALREADY (already authenticated) %s") - (s908 . "RPL_SASLMECHS (unsupported mechanism %m) %s"))) + (s908 . "RPL_SASLMECHS (unsupported mechanism: %m) %s"))) (define-erc-module sasl nil "Non-IRCv3 SASL support for ERC. @@ -362,12 +355,11 @@ sasl (client (erc-sasl--create-client mech))) (unless client (erc-display-error-notice - nil (format "Unknown SASL mechanism: %s" mech)) - (erc-error "Unknown SASL mechanism: %s" mech)) + 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) - (setf (alist-get erc-networks--id erc-sasl--session-options nil t) nil) (kill-local-variable 'erc-sasl--state) (kill-local-variable 'erc-sasl--options)) 'local) @@ -393,7 +385,6 @@ 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) @@ -411,8 +402,9 @@ erc-sasl--destroy (define-erc-response-handler (908) "Handle a RPL_SASLALREADY response." nil (erc-display-message parsed '(notice error) 'active 's908 - '?m (alist-get 'mechanism erc-sasl--options) - '?s (erc-response.contents parsed)) + ?m (alist-get 'mechanism erc-sasl--options) + ?s (string-join (cdr (erc-response.command-args parsed)) + " ")) (erc-sasl--destroy proc)) (cl-defmethod erc--register-connection (&context (erc-sasl-mode (eql t))) @@ -421,7 +413,11 @@ erc--register-connection (m (sasl-mechanism-name (sasl-client-mechanism c)))) (progn (erc-server-send "CAP REQ :sasl") - (erc-login) + (if (and erc-session-password + (eq :password (alist-get 'password erc-sasl--options))) + (let (erc-session-password) + (erc-login)) + (erc-login)) (erc-server-send (format "AUTHENTICATE %s" m))) (erc-sasl--destroy erc-server-process))) diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el index ebec8846b1..60bfb909e0 100644 --- a/lisp/erc/erc.el +++ b/lisp/erc/erc.el @@ -1951,7 +1951,7 @@ erc-open (with-suppressed-warnings ((obsolete erc-reuse-buffers)) erc-reuse-buffers) - erc-networks--id))) + (buffer-local-variables)))) (when connect (run-hook-with-args 'erc-before-connect server port nick)) (set-buffer buffer) (setq old-point (point)) @@ -2021,7 +2021,8 @@ erc-open (setq erc-session-client-certificate client-certificate) (setq erc-networks--id (if connect - (or erc--server-reconnecting + (or (and erc--server-reconnecting + (alist-get 'erc-networks--id erc--server-reconnecting)) (and id (erc-networks--id-create id))) (buffer-local-value 'erc-networks--id old-buffer))) ;; debug output buffer diff --git a/test/lisp/erc/erc-sasl-tests.el b/test/lisp/erc/erc-sasl-tests.el index 81db9ad948..20a6760083 100644 --- a/test/lisp/erc/erc-sasl-tests.el +++ b/test/lisp/erc/erc-sasl-tests.el @@ -32,61 +32,83 @@ erc-sasl--mechanism-offered-p (should-not (erc-sasl--mechanism-offered-p "fooexternal")) (should-not (erc-sasl--mechanism-offered-p "externalbar")))) -(ert-deftest erc-sasl--read-password () +(ert-deftest erc-sasl--read-password--basic () (ert-info ("Explicit erc-sasl-password") (let ((erc-sasl--options '((password . "foo")))) (should (string= (erc-sasl--read-password nil) "foo")))) - (ert-info ("Fallback to erc-session-password") - (let ((erc-session-password "bar") - (erc-networks--id (erc-networks--id-create nil))) - (should (string= (erc-sasl--read-password nil) "bar"))) + (ert-info ("Explicit session password") + (let ((erc-session-password "foo") + (erc-sasl--options '((password . :password)))) + (should (string= (erc-sasl--read-password nil) "foo")))) + + (ert-info ("Fallback to prompt skip auth-source") + (should-not erc-sasl-auth-source-function) (let ((erc-session-password "bar") - (erc-sasl--options '((user . "tester") (password))) (erc-networks--id (erc-networks--id-create nil))) - (should (string= (erc-sasl--read-password nil) "bar")))) + (should (string= (ert-simulate-keys "bar\r" + (erc-sasl--read-password "?")) + "bar")))) - (let* ((entries (list - "machine FSF.chat port 6697 user bob password sesame" - ;; This must come *after* ^, else *1 (below) always passes - "machine GNU/chat port 6697 user bob password spam" - "machine MyHost port irc password 123")) - (netrc-file (make-temp-file "auth-source-test" nil nil - (mapconcat 'identity entries "\n"))) - (auth-sources (list netrc-file)) - (erc-session-server "irc.gnu.org") - (erc-session-port 6697) - (erc-networks--id (erc-networks--id-create nil)) - ;; - (erc-sasl-auth-source-function #'erc--auth-source-search) - erc-server-announced-name ; too early - auth-source-do-cache) + (ert-info ("Prompt when auth-source fails and `erc-sasl-password' null") + (let ((erc-sasl--options '((password))) + (erc-sasl-auth-source-function #'ignore)) + (should (string= (ert-simulate-keys "baz\r" + (erc-sasl--read-password "pwd:")) + "baz"))))) - (unwind-protect - (ert-info ("Auth source") +(ert-deftest erc-sasl--read-password--auth-source () + (ert-with-temp-file netrc-file + :text (string-join + (list + ;; If you swap these first 2 lines, *1 below fails + "machine FSF.chat port 6697 user bob password sesame" + "machine GNU/chat port 6697 user bob password spam" + "machine MyHost port irc password 123") + "\n") + (let* ((auth-sources (list netrc-file)) + (erc-session-server "irc.gnu.org") + (erc-session-port 6697) + (erc-networks--id (erc-networks--id-create nil)) + calls + (erc-sasl-auth-source-function + (lambda (&rest r) + (push r calls) + (apply #'erc--auth-source-search r))) + erc-server-announced-name ; too early + auth-source-do-cache) - (ert-info ("Symbol as password specifies machine") - (let ((erc-sasl--options '((user . "bob") - (password . FSF.chat))) - (erc-networks--id (make-erc-networks--id))) - (should (string= (erc-sasl--read-password nil) "sesame")))) + (ert-info ("Symbol as password specifies machine") + (let ((erc-sasl--options '((user . "bob") (password . FSF.chat))) + (erc-networks--id (make-erc-networks--id))) + (should (string= (erc-sasl--read-password nil) "sesame")) + (should (equal (pop calls) '(:user "bob" :host "FSF.chat"))))) - (ert-info ("Use session ID when password empty") ; *1 - (let ((erc-sasl--options '((user . "bob") (password))) - (erc-networks--id (erc-networks--id-create 'GNU/chat))) - (should (string= (erc-sasl--read-password nil) "spam"))))) + (ert-info ("ID for :host and `erc-session-username' for :user") ; *1 + (let ((erc-session-username "bob") + (erc-sasl--options '((user . :user) (password))) + (erc-networks--id (erc-networks--id-create 'GNU/chat))) + (should (string= (erc-sasl--read-password nil) "spam")) + (should (equal (pop calls) '(:user "bob" :host "GNU/chat"))))) - (delete-file netrc-file)) + (ert-info ("ID for :host and current nick for :user") ; *1 + (let ((erc-server-current-nick "bob") + (erc-sasl--options '((user . :nick) (password))) + (erc-networks--id (erc-networks--id-create 'GNU/chat))) + (should (string= (erc-sasl--read-password nil) "spam")) + (should (equal (pop calls) '(:user "bob" :host "GNU/chat"))))) - (ert-info ("Prompt when search fails and server password null") - (let ((erc-sasl-auth-source-function #'ignore)) - (should (string= (ert-simulate-keys "baz\r" - (erc-sasl--read-password "pwd:")) - "baz")))))) + (ert-info ("Symbol as password, entry lacks user field") + (let ((erc-server-current-nick "fake") + (erc-sasl--options '((user . :nick) (password . MyHost))) + (erc-networks--id (erc-networks--id-create 'GNU/chat))) + (should (string= (erc-sasl--read-password nil) "123")) + (should (equal (pop calls) '(:user "fake" :host "MyHost")))))))) (ert-deftest erc-sasl-create-client--plain () (let* ((erc-session-password "password123") - (erc-server-current-nick "tester") + (erc-session-username "tester") + (erc-sasl--options '((user . :user) (password . :password))) (erc-session-port 1667) (erc-session-server "localhost") (client (erc-sasl--create-client 'plain)) @@ -100,7 +122,8 @@ erc-sasl-create-client--plain (ert-deftest erc-sasl-create-client--external () (let* ((erc-server-current-nick "tester") - (client (erc-sasl--create-client 'external)) + (erc-sasl--options '((user . :nick) (password . :password))) + (client (erc-sasl--create-client 'external)) ; unused ^ (result (sasl-next-step client nil))) (should (equal (format "%S" [ignore nil]) (format "%S" result))) (should-not (sasl-step-data result)) @@ -109,9 +132,8 @@ erc-sasl-create-client--external (should-not (assoc-default "EXTERNAL" sasl-mechanism-alist))) (ert-deftest erc-sasl-create-client--scram-sha-1 () - (let* ((erc-server-current-nick "jilles") - (erc-session-password "sesame") - (erc-sasl--options '((authzid . "jilles"))) + (let* ((erc-sasl--options '((user . "jilles") (password . "sesame") + (authzid . "jilles"))) (mock-rvs (list "c5RqLCZy0L4fGkKAZ0hujFBs" "")) (sasl-unique-id-function (lambda () (pop mock-rvs))) (client (erc-sasl--create-client 'scram-sha-1)) @@ -149,7 +171,8 @@ erc-sasl-create-client--scram-sha-256 (ert-skip "Emacs lacks sasl-scram-sha256")) (let* ((erc-server-current-nick "jilles") (erc-session-password "sesame") - (erc-sasl--options '((authzid . "jilles"))) + (erc-sasl--options '((user . :nick) (password . :password) + (authzid . "jilles"))) (mock-rvs (list "c5RqLCZy0L4fGkKAZ0hujFBs" "")) (sasl-unique-id-function (lambda () (pop mock-rvs))) (client (erc-sasl--create-client 'scram-sha-256)) @@ -189,6 +212,7 @@ erc-sasl-create-client--scram-sha-256--no-authzid (ert-skip "Emacs lacks sasl-scram-sha256")) (let* ((erc-server-current-nick "jilles") (erc-session-password "sesame") + (erc-sasl--options '((user . :nick) (password . :password) (authzid))) (mock-rvs (list "c5RqLCZy0L4fGkKAZ0hujFBs" "")) (sasl-unique-id-function (lambda () (pop mock-rvs))) (client (erc-sasl--create-client 'scram-sha-256)) @@ -228,6 +252,7 @@ erc-sasl-create-client--scram-sha-512--no-authzid (ert-skip "Emacs lacks sasl-scram-sha512")) (let* ((erc-server-current-nick "jilles") (erc-session-password "sesame") + (erc-sasl--options '((user . :nick) (password . :password) (authzid))) (mock-rvs (list "c5RqLCZy0L4fGkKAZ0hujFBs" "")) (sasl-unique-id-function (lambda () (pop mock-rvs))) (client (erc-sasl--create-client 'scram-sha-512)) diff --git a/test/lisp/erc/erc-scenarios-sasl.el b/test/lisp/erc/erc-scenarios-sasl.el index 7970e65ec2..713c9929c3 100644 --- a/test/lisp/erc/erc-scenarios-sasl.el +++ b/test/lisp/erc/erc-scenarios-sasl.el @@ -39,9 +39,7 @@ erc-scenarios-sasl--plain (dumb-server (erc-d-run "localhost" t 'plain)) (port (process-contact dumb-server :service)) (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))) @@ -71,7 +69,6 @@ erc-scenarios-sasl--local-modules-reconnect (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))) @@ -94,7 +91,6 @@ erc-scenarios-sasl--local-modules-reconnect (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))) @@ -114,7 +110,6 @@ 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))) @@ -144,7 +139,6 @@ 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)) @@ -172,9 +166,8 @@ erc-scenarios--common--sasl (dumb-server (erc-d-run "localhost" t mech)) (port (process-contact dumb-server :service)) (erc-modules (cons 'sasl erc-modules)) - (erc-sasl-password "sesame") + (erc-sasl-user :nick) (erc-sasl-mechanism mech) - (erc-sasl--session-options nil) (mock-rvs (list "c5RqLCZy0L4fGkKAZ0hujFBs" "")) (sasl-unique-id-function (lambda () (pop mock-rvs))) (inhibit-message noninteractive) @@ -184,6 +177,7 @@ erc-scenarios--common--sasl (with-current-buffer (erc :server "127.0.0.1" :port port :nick "jilles" + :password "sesame" :full-name "jilles") (should (string= (buffer-name) (format "127.0.0.1:%d" port))))) -- 2.38.1