From 5e8fd5c54b46286565d938d9984c26d44f194bf0 Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Thu, 13 Oct 2022 19:52:09 -0700 Subject: [PATCH 0/4] *** NOT A PATCH *** *** BLURB HERE *** F. Jason Park (4): Add GS2 authorization to sasl-scram-rfc Support local ERC modules in erc-mode buffers Make erc-login generic Add non-IRCv3 SASL module to ERC doc/misc/erc.texi | 138 +++++- lisp/erc/erc-backend.el | 8 +- lisp/erc/erc-compat.el | 104 +++++ lisp/erc/erc-sasl.el | 396 ++++++++++++++++++ lisp/erc/erc.el | 84 ++-- lisp/net/sasl-scram-rfc.el | 21 +- test/lisp/erc/erc-sasl-tests.el | 302 +++++++++++++ test/lisp/erc/erc-scenarios-sasl.el | 161 +++++++ test/lisp/erc/erc-tests.el | 47 +++ test/lisp/erc/resources/sasl/external.eld | 33 ++ test/lisp/erc/resources/sasl/plain-failed.eld | 16 + test/lisp/erc/resources/sasl/plain.eld | 35 ++ test/lisp/erc/resources/sasl/scram-sha-1.eld | 47 +++ .../lisp/erc/resources/sasl/scram-sha-256.eld | 47 +++ 14 files changed, 1400 insertions(+), 39 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 3b7af0fb1b..80b4171cdb 100644 --- a/doc/misc/erc.texi +++ b/doc/misc/erc.texi @@ -957,18 +957,6 @@ SASL (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}. - -When @code{erc-sasl-password} is a string, it's used unconditionally. -When it's a non-@code{nil} symbol, like @samp{Libera.Chat}, it's used -as the @code{:host} param in an auth-source query. When it's -@code{nil} and a session ID is on file, the ID is used instead for the -@code{:host} param (@pxref{Network Identifier}). The value of -@code{erc-sasl-user} is always specified for the @code{:user} -(@code{:login}) param. - -If a password can't be determined, a non-@code{nil} server -(connection) password will be tried. (This may change, however, so -please don't rely on it.) @end indentedblock @var{external} (via Client TLS Certificate): @@ -1001,12 +989,11 @@ SASL @indentedblock This mechanism is quite complicated and currently requires the -presence of the external @samp{openssl} command-line utility, so -please use something else if at all possible. Ignoring that, specify -your key file (e.g., @samp{~/pki/mykey.pem}) as the value of -@code{erc-sasl-password}, and then configure your network settings. -On servers running Atheme services, you can add your public key with -@samp{NickServ} like so: +external @samp{openssl} executable, so please use something else if at +all possible. Ignoring that, specify your key file (e.g., +@samp{~/pki/mykey.pem}) as the value of @code{erc-sasl-password}, and +then configure your network settings. On servers running Atheme +services, you can add your public key with @samp{NickServ} like so: @example ERC> /msg NickServ set property \ @@ -1019,26 +1006,34 @@ SASL @end defopt @defopt erc-sasl-user -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 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. @end defopt @defopt erc-sasl-password -Optional account password to send when authenticating. - -If you specify a string, it'll be considered authoritative and -accepted at face value. If you instead give a non-@code{nil} symbol, -it'll be passed as the value of the @code{:host} field in an -auth-source query, provided @code{erc-sasl-auth-source-function} is -set to a function. If you set this to @code{nil}, a non-@code{nil} -``session password'' will be tried, likely whatever you gave as the -@var{password} argument to @code{erc-tls}. As a last resort, you'll -be prompted for input. - -Note that when @code{erc-sasl-mechanism} is set to -@code{ecdsa-nist256p-challenge}, this option should hold the file name -of your key, which is typically in PEM format. +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. +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 +option should instead hold the file name of your key. @end defopt @defopt erc-sasl-auth-source-function diff --git a/lisp/erc/erc-sasl.el b/lisp/erc/erc-sasl.el index d237ab73a8..f36a305247 100644 --- a/lisp/erc/erc-sasl.el +++ b/lisp/erc/erc-sasl.el @@ -19,28 +19,24 @@ ;;; Commentary: -;; WARNING: this is a (non-IRCv3) implementation of SASL. Please see -;; bug#49860, which adds full 3.2 capability negotiation. -;; -;; Various ERC implementations of the PLAIN mechanism have surfaced -;; over the years, the first possibly being: +;; This "non-IRCv3" implementation resembles many others that have +;; surfaced over the years, the first possibly being: ;; ;; https://lists.gnu.org/archive/html/erc-discuss/2012-02/msg00001.html ;; -;; This module would not exist without this and other pioneering -;; efforts. +;; See options and Info manual for usage. ;; ;; TODO: ;; ;; - Find a way to obfuscate the password in memory (via something -;; - like `auth-source--obfuscate'); it's currently visible in -;; - backtraces. +;; like `auth-source--obfuscate'); it's currently visible in +;; backtraces. ;; -;; - Implement pseudo PASSWORD mechanism that chooses the strongest -;; available mechanism for you. +;; - Implement a proxy mechanism that chooses the strongest available +;; mechanism for you. Requires CAP 3.2 (see bug#49860). ;;; Code: -(require 'erc-backend) +(require 'erc) (require 'rx) (require 'sasl) (require 'sasl-scram-rfc) @@ -72,13 +68,13 @@ erc-sasl-user (defcustom erc-sasl-password nil "Optional account password to send when authenticating. -When the value is a string, it's used unconditionally. As a -special case, when the value is a non-nil symbol, it's used 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, a non-nil \"session password\" will be tried, likely one -given as the `:password' argument to `erc-tls'. As a last -resort, the user will be prompted for input. +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. Note that when `erc-sasl-mechanism' is set to `ecdsa-nist256p-challenge', this option should hold the file name @@ -100,9 +96,7 @@ erc-sasl-auth-source-function function)) (defcustom erc-sasl-authzid nil - "SASL authorization identity. -Generally unneeded for normal use. Some test frameworks and -aberrant servers may want this to match `erc-sasl-user'." + "SASL authorization identity, likely unneeded for everyday use." :type '(choice (const nil) string)) @@ -121,24 +115,22 @@ erc-sasl--state (defun erc-sasl--read-password (prompt) "Return configured option or server password. PROMPT is passed to `read-passwd' if necessary." - ;; Copying prevent `sasl-plain-response' from clobbering - (if-let - ((found - (or (and-let* ((pass (alist-get 'password erc-sasl--options)) - ((stringp pass)) - (pass))) - (and erc-sasl-auth-source-function - (let ((user (alist-get 'user erc-sasl--options)) - (host (alist-get 'password erc-sasl--options))) - (apply erc-sasl-auth-source-function - `(,@(and user (list :user user)) - ,@(and host (list :host (symbol-name host))))))) - erc-session-password))) - (copy-sequence found) - (read-passwd prompt))) + (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 found) + (read-passwd prompt)))) (defun erc-sasl--plain-response (client steps) - "Call `sasl-plain-response' with CLIENT and STEPS." (let ((sasl-read-passphrase #'erc-sasl--read-password)) (sasl-plain-response client steps))) @@ -146,9 +138,6 @@ erc-sasl--plain-response (hash-fun block-length hash-length client step)) (defun erc-sasl--scram-sha-hack-client-final-message (&rest args) - "Call `sasl-scram--client-final-message' with args. -Pass HASH-FUN, BLOCK-LENGTH, HASH-LENGTH, CLIENT, and STEP -directly upstream." ;; In the future (29+), we'll hopefully be able to call ;; `sasl-scram--client-final-message' directly (require 'erc-compat) @@ -156,27 +145,22 @@ erc-sasl--scram-sha-hack-client-final-message (apply #'erc-compat--sasl-scram--client-final-message args))) (defun erc-sasl--scram-sha-1-client-final-message (client step) - "Prepare CLIENT's final message with STEP." (erc-sasl--scram-sha-hack-client-final-message 'sha1 64 20 client step)) (defun erc-sasl--scram-sha-256-client-final-message (client step) - "Prepare CLIENT's final message with STEP." (erc-sasl--scram-sha-hack-client-final-message 'sasl-scram-sha256 64 32 - client step)) + client step)) (defun erc-sasl--scram-sha512 (object &optional start end binary) - "Pass OBJECT, START, END, and BINARY to `secure-hash'." (secure-hash 'sha512 object start end binary)) (defun erc-sasl--scram-sha-512-client-final-message (client step) - "Prepare CLIENT's final message with STEP." - (erc-sasl--scram-sha-hack-client-final-message - #'erc-sasl--scram-sha512 128 64 client step)) + (erc-sasl--scram-sha-hack-client-final-message #'erc-sasl--scram-sha512 + 128 64 client step)) (defun erc-sasl--scram-sha-512-authenticate-server (client step) - "Call `sasl-scram--authenticate-server' with CLIENT and STEP." - (sasl-scram--authenticate-server - #'erc-sasl--scram-sha512 128 64 client step)) + (sasl-scram--authenticate-server #'erc-sasl--scram-sha512 + 128 64 client step)) (defun erc-sasl--ecdsa-first (client _step) "Return CLIENT name." @@ -184,7 +168,7 @@ erc-sasl--ecdsa-first ;; FIXME do this with gnutls somehow (defun erc-sasl--ecdsa-sign (client step) - "Return signed challenge for CLIENT and STEP." + "Return signed challenge for CLIENT and current STEP." (let ((challenge (sasl-step-data step))) (with-temp-buffer (set-buffer-multibyte nil) @@ -195,9 +179,6 @@ erc-sasl--ecdsa-sign "-sign") (buffer-string)))) -;; This API may seem roundabout, but the "template method" here is -;; one that we provide, namely `erc-sasl--authenticate-handler'. - (pcase-dolist (`(,name . ,steps) '(("PLAIN" @@ -240,13 +221,9 @@ erc-sasl--create-client (alist-get 'authzid erc-sasl--options)) client)) -;; Oragono doesn't like when authzid (if present) does not match -;; the authcid. TODO see if this still true. - (cl-defmethod erc-sasl--create-client ((_m (eql plain))) - "Create and return new SASL PLAIN client object. -See message breakdown at -https://tools.ietf.org/html/rfc4616#section-2." + "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)) @@ -264,19 +241,19 @@ erc-sasl--create-client client)) (cl-defmethod erc-sasl--create-client ((m (eql scram-sha-256))) - "Create a SCRAM-SHA-256 client." + "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)) (cl-defmethod erc-sasl--create-client ((m (eql scram-sha-512))) - "Create a SCRAM-SHA-512 client." + "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)) (cl-defmethod erc-sasl--create-client ((_ (eql ecdsa-nist256p-challenge))) - "Create a ECDSA-NIST256P-CHALLENGE client." + "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)))) @@ -286,6 +263,7 @@ erc-sasl--create-client (sasl-client-set-property client 'ecdsa-keyfile keyfile) client))) +;; 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) @@ -294,7 +272,7 @@ erc-sasl--init (authzid . ,erc-sasl-authzid)))) (defun erc-sasl--mechanism-offered-p (offered) - "Non-nil when mechanism OFFERED by server." + "Return non-nil when OFFERED appears among a list of mechanisms." (string-match-p (rx-to-string `(: (| bot ",") ,(symbol-name @@ -341,10 +319,10 @@ erc-sasl--authenticate-handler (s908 . "RPL_SASLMECHS (unsupported mechanism %m) %s"))) (define-erc-module sasl nil - "Non-IRCv3 (dumb) SASL support for ERC. -Needless to say, this doesn't solicit or validate a suite of -supported mechanisms. See bug#49860 for a full, CAP 3.2-aware -implementation, currently a WIP as of ERC 5.5." + "Non-IRCv3 SASL support for ERC. +This doesn't solicit or validate a suite of supported mechanisms." + ;; See bug#49860 for a full, CAP 3.2-aware implementation, currently + ;; a WIP as of ERC 5.5. ((unless erc--target (add-hook 'erc-server-AUTHENTICATE-functions #'erc-sasl--authenticate-handler 0 t) diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el index 1778480df1..7c72085fea 100644 --- a/lisp/erc/erc.el +++ b/lisp/erc/erc.el @@ -1390,9 +1390,7 @@ define-erc-module This will define a minor mode called erc-NAME-mode, possibly an alias erc-ALIAS-mode, as well as the helper functions -erc-NAME-enable, and erc-NAME-disable. Beware that for global -modules, these helpers, as well as the minor-mode toggle, all mutate -the user option `erc-modules'. +erc-NAME-enable, and erc-NAME-disable. Example: @@ -1428,21 +1426,16 @@ 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)) + (add-to-list 'erc-modules (quote ,name)) + (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)) + (setq erc-modules (delq (quote ,name) erc-modules)) + (setq ,mode nil) + ,@disable-body) ,(when (and alias (not (eq name alias))) `(defalias ',(intern @@ -2062,10 +2055,6 @@ erc--module-name-migrations pairs) "Association list of obsolete module names to canonical names.") -(defun erc--normalize-module-symbol (module) - "Canonicalize symbol MODULE for `erc-modules'." - (or (cdr (assq module erc--module-name-migrations)) module)) - (defun erc-migrate-modules (mods) "Migrate old names of ERC modules to new ones." ;; modify `transforms' to specify what needs to be changed @@ -2132,6 +2121,7 @@ erc-modules (const :tag "readonly: Make displayed lines read-only" readonly) (const :tag "replace: Replace text in messages" replace) (const :tag "ring: Enable an input history" ring) + (const :tag "sasl: Enable SASL authentication" sasl) (const :tag "scrolltobottom: Scroll to the bottom of the buffer" scrolltobottom) (const :tag "services: Identify to Nickserv (IRC Services) automatically" diff --git a/test/lisp/erc/erc-sasl-tests.el b/test/lisp/erc/erc-sasl-tests.el index c54acc4d28..112303baf5 100644 --- a/test/lisp/erc/erc-sasl-tests.el +++ b/test/lisp/erc/erc-sasl-tests.el @@ -47,16 +47,18 @@ erc-sasl--read-password (should (string= (erc-sasl--read-password nil) "bar")))) (let* ((entries (list - "machine GNU/chat port 6697 user bob password spam" "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-sasl-auth-source-function #'erc--auth-source-search) erc-server-announced-name ; too early auth-source-do-cache) @@ -69,7 +71,7 @@ erc-sasl--read-password (erc-networks--id (make-erc-networks--id))) (should (string= (erc-sasl--read-password nil) "sesame")))) - (ert-info ("Use session ID when password empty") + (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"))))) -- 2.37.3