From b2e7df6b097b4b203860189dd59219909959c016 Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Sun, 18 Sep 2022 01:37:13 -0700 Subject: [PATCH 4/4] Add non-IRCv3 SASL module to ERC * lisp/erc/erc-compat.el (erc-compat--sasl-scram-construct-gs2-header, erc-compat--sasl-scram-client-first-message, erc-compat--sasl-scram--client-final-message): Add minimal authorization support via own variant of `sasl-scram--client-final-message' and supporting sasl-scram-rfc functions introduced in Emacs 29. * lisp/erc/erc-sasl.el: New file. * test/lisp/erc/erc-sasl-tests.el: New file. * test/lisp/erc/erc-scenarios-sasl.el: New file. * test/lisp/erc/resources/sasl/plain-failed.eld: New file. * test/lisp/erc/resources/sasl/plain.eld: New file. * test/lisp/erc/resources/sasl/scram-sha-1.eld: New file. * test/lisp/erc/resources/sasl/scram-sha-256.eld: New file. * test/lisp/erc/resources/sasl/external.eld: New file. --- lisp/erc/erc-compat.el | 104 ++++ lisp/erc/erc-sasl.el | 477 ++++++++++++++++++ test/lisp/erc/erc-sasl-tests.el | 299 +++++++++++ test/lisp/erc/erc-scenarios-sasl.el | 161 ++++++ 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 ++ 9 files changed, 1219 insertions(+) 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 diff --git a/lisp/erc/erc-compat.el b/lisp/erc/erc-compat.el index 8a00e711ac..3123f64b88 100644 --- a/lisp/erc/erc-compat.el +++ b/lisp/erc/erc-compat.el @@ -156,6 +156,110 @@ erc-subseq (setq i (1+ i) start (1+ start))) res)))))) + +;;;; SASL + +(declare-function sasl-step-data "sasl" (step)) +(declare-function sasl-error "sasl" (datum)) +(declare-function sasl-client-property "sasl" (client property)) +(declare-function sasl-client-set-property "sasl" (client property value)) +(declare-function sasl-mechanism-name "sasl" (mechanism)) +(declare-function sasl-client-name "sasl" (client)) +(declare-function sasl-client-mechanism "sasl" (client)) +(declare-function sasl-read-passphrase "sasl" (prompt)) +(declare-function sasl-unique-id "sasl" nil) +(declare-function decode-hex-string "hex-util" (string)) +(declare-function rfc2104-hash "rfc2104" (hash block-length hash-length + key text)) +(declare-function sasl-scram--client-first-message-bare "sasl-scram-rfc" + (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. + (let ((authzid (sasl-client-property client 'authenticator-name))) + (concat "n," (and authzid "a=") authzid ","))) + +(defun erc-compat--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) + (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, 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. +;; +;; 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 + (hash-fun block-length hash-length client step) + (unless (string-match + "^r=\\([^,]+\\),s=\\([^,]+\\),i=\\([0-9]+\\)\\(?:$\\|,\\)" + (sasl-step-data step)) + (sasl-error "Unexpected server response")) + (let* ((hmac-fun + (lambda (text key) + (decode-hex-string + (rfc2104-hash hash-fun block-length hash-length key text)))) + (step-data (sasl-step-data step)) + (nonce (match-string 1 step-data)) + (salt-base64 (match-string 2 step-data)) + (iteration-count (string-to-number (match-string 3 step-data))) + (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 + (sasl-error "Invalid nonce from server"))) + (client-final-message-without-proof + (concat "c=" (base64-encode-string cbind-input t) "," ; *2 + "r=" nonce)) + (password + (sasl-read-passphrase + (format "%s passphrase for %s: " + (sasl-mechanism-name (sasl-client-mechanism client)) + (sasl-client-name client)))) + (salt (base64-decode-string salt-base64)) + (string-xor (lambda (a b) + (apply #'unibyte-string (cl-mapcar #'logxor a b)))) + (salted-password (let ((digest (concat salt (string 0 0 0 1))) + (xored nil)) + (dotimes (_i iteration-count xored) + (setq digest (funcall hmac-fun digest password)) + (setq xored (if (null xored) + digest + (funcall string-xor xored + digest)))))) + (client-key (funcall hmac-fun "Client Key" salted-password)) + (stored-key (decode-hex-string (funcall hash-fun client-key))) + (auth-message (concat "n=" (sasl-client-name client) + ",r=" c-nonce "," step-data + "," client-final-message-without-proof)) + (client-signature (funcall hmac-fun + (encode-coding-string auth-message 'utf-8) + stored-key)) + (client-proof (funcall string-xor client-key client-signature)) + (client-final-message + (concat client-final-message-without-proof "," + "p=" (base64-encode-string client-proof t)))) ; *3 + (sasl-client-set-property client 'auth-message auth-message) + (sasl-client-set-property client 'salted-password salted-password) + client-final-message)) + (provide 'erc-compat) ;;; erc-compat.el ends here diff --git a/lisp/erc/erc-sasl.el b/lisp/erc/erc-sasl.el new file mode 100644 index 0000000000..bd27934125 --- /dev/null +++ b/lisp/erc/erc-sasl.el @@ -0,0 +1,477 @@ +;;; erc-sasl.el --- SASL for ERC -*- lexical-binding: t -*- + +;; Copyright (C) 2022 Free Software Foundation, Inc. +;; +;; This file is part of GNU Emacs. +;; +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published +;; by the Free Software Foundation, either version 3 of the License, +;; or (at your option) any later version. +;; +;; GNU Emacs is distributed in the hope that it will be useful, but +;; WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +;; General Public License for more details. +;; +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see . + +;;; Commentary: + +;; WARNING: this is a naive/hack (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: +;; +;; https://lists.gnu.org/archive/html/erc-discuss/2012-02/msg00001.html +;; +;; This module would not exist without this and other pioneering +;; efforts. +;; +;; FIXME move the following to doc/misc/erc.texi +;; +;; Regardless of the mechanism or server, you'll likely have to be +;; registered before first use. 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 client -> bouncer connection. +;; +;; Note that `sasl' is a "local" ERC module (effectively introduced in +;; ERC 5.5). This means invoking `erc-sasl-mode' manually or calling +;; `erc-update-modules' won't do any good. Instead, simply add `sasl' +;; to `erc-modules' or `let'-bind it while calling `erc-tls', and SASL +;; will be enabled for the current connection. But before that, +;; please explore all custom options that pertain to your chosen +;; mechanism. +;; +;; Password-based mechanisms: +;; +;; Here, "password" refers to your account password, which is +;; usually your NickServ password. This often differs from any +;; connection (server) password given to `erc-tls' via its :password +;; arg. To make this work, customize both `erc-sasl-user' and +;; `erc-sasl-password' or bind them when invoking `erc-tls'. +;; +;; When `erc-sasl-password' is a string, it's used unconditionally. +;; When it's a non-nil symbol, like Libera.Chat, it's used as the +;; host param in an auth-source query. When it's nil and a session +;; ID is on file (see `erc-tls'), the ID is instead used for the +;; host param. The value of `erc-sasl-user' is always specified for +;; the user (login) param. See the info node "(erc) Connecting" for +;; specifics. +;; +;; If no password can be determined, a non-nil connection password +;; will be tried (but this may change, so please don't rely on it). +;; +;; EXTERNAL (with Client TLS Certificate): +;; +;; 1. Specify the `:client-certificate' param when opening a new +;; connection, which is typically done by calling `emacs-tls'. +;; See (info "(erc) Connecting"). +;; +;; 2. Ensure you've registered your fingerprint with the network and +;; (re)connect. The fingerprint is usually a SHA1 or SHA256 +;; digest in either "normalized" or "openssl" forms. The first +;; is lowercase without delims ("deadbeef") and the second +;; uppercase with colon seps ("DE:AD:BE:EF"). +;; +;; There's no reason to send your password after registering. Note +;; that most ircds will allow you to authenticate with a client cert +;; but without the hassle of SASL (meaning you may not need this +;; module). +;; +;; ECDSA-NIST256P-CHALLENGE: +;; +;; Use something else if at all possible. This currently requires +;; the openssl command-line utility. On servers running Atheme +;; services, add your public key with NickServ like so: +;; +;; /msg NickServ set property +;; pubkey AgGZmlYTUjJlea/BVz7yrjJ6gysiAPaQxzeUzTH4hd5j +;; +;; (You may not need the "property" subcommand.) +;; +;; +;; TODO +;; +;; - Implement pseudo PASSWORD mechanism that chooses the strongest +;; available mechanism for you. +;; +;; - Maybe provide explicit authz. Currently, there's only an obscure +;; customizable function option for SCRAM and nothing for plain. + +;;; Code: +(require 'erc-backend) +(require 'rx) +(require 'sasl) +(require 'sasl-scram-rfc) +(require 'sasl-scram-sha256 nil t) + +(defgroup erc-sasl nil + "SASL for ERC." + :group 'erc + :package-version '(ERC . "5.4")) ; FIXME increment on next release + +(defcustom erc-sasl-mechanism nil + "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) + (const external) + (const scram-sha-1) + (const scram-sha-256) + (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 + "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." + :type '(choice (const nil) 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." + :type '(choice (const erc-auth-source-search) + (const nil) + function)) + +(defcustom erc-sasl-ecdsa-private-key nil + "Private signing key file for ECDSA-NIST256P-CHALLENGE." + :type '(choice (const nil) string)) + +(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'." + :type '(choice (const nil) string)) + + +;; Analogous to what erc-backend does to persist opening params. +(defvar-local erc-sasl--options nil) + +;; Session-local (server buffer) SASL subproto state +(defvar-local erc-sasl--state nil) + +(cl-defstruct erc-sasl--state + "Holder for client object and subproto state." + (client nil :type vector) + (step nil :type vector) + (pending nil :type string)) + +(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))) + +(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))) + +(declare-function erc-compat--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) + "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) + (let ((sasl-read-passphrase #'erc-sasl--read-password)) + (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)) + +(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)) + +(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)) + +(defun erc-sasl--ecdsa-first (client _step) + "Return CLIENT name." + (sasl-client-name client)) + +;; FIXME do this with gnutls somehow +(defun erc-sasl--ecdsa-sign (_client step) + "Return signed challenge for CLIENT and STEP." + (let ((challenge (sasl-step-data step))) + (with-temp-buffer + (set-buffer-multibyte nil) + (insert challenge) + (call-process-region (point-min) (point-max) + "openssl" 'delete t nil "pkeyutl" "-inkey" + (alist-get 'ecdsa-private-key erc-sasl--options) + "-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" + erc-sasl--plain-response) + ("EXTERNAL" + ignore) + ("SCRAM-SHA-1" + erc-compat--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-sasl--scram-sha-256-client-final-message + sasl-scram-sha-256-authenticate-server) + ("SCRAM-SHA-512" + erc-compat--sasl-scram-client-first-message + erc-sasl--scram-sha-512-client-final-message + erc-sasl--scram-sha-512-authenticate-server) + ("ECDSA-NIST256P-CHALLENGE" + erc-sasl--ecdsa-first + erc-sasl--ecdsa-sign))) + (let ((feature (intern (concat "erc-sasl-" (downcase name))))) + (put feature 'sasl-mechanism (sasl-make-mechanism name steps)) + (provide feature))) + +(cl-defgeneric erc-sasl--create-client (mechanism) + "Create and return a new SASL client object for MECHANISM." + (let ((sasl-mechanism-alist (copy-sequence sasl-mechanism-alist)) + (sasl-mechanisms sasl-mechanisms) + (name (upcase (symbol-name mechanism))) + (feature (intern (concat "erc-sasl-" (symbol-name mechanism)))) + client) + (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))) + "N/A" "N/A")) + (sasl-client-set-property client 'authenticator-name + (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." + (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)))) + (port (if (numberp erc-session-port) + (number-to-string erc-session-port) + "0")) + ;; In most cases, `erc-server-announced-name' won't be known. + (host (or erc-server-announced-name erc-session-server)) + (mech (sasl-find-mechanism '("PLAIN"))) + (client (sasl-make-client mech authc port host))) + (sasl-client-set-property client 'authenticator-name + (alist-get 'authzid erc-sasl--options)) + client)) + +(cl-defmethod erc-sasl--create-client ((m (eql scram-sha-256))) + "Create a 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." + (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." + (unless (executable-find "openssl") + (user-error "Could not find openssl command-line utility")) + (unless (and (alist-get 'ecdsa-private-key erc-sasl--options) + (file-exists-p (alist-get 'ecdsa-private-key + erc-sasl--options))) + (user-error "Could not find `erc-sasl-ecdsa-private-key'")) + (cl-call-next-method)) + +(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) + (ecdsa-private-key . ,erc-sasl-ecdsa-private-key) + (authzid . ,erc-sasl-authzid)))) + +(defun erc-sasl--mechanism-offered-p (offered) + "Non-nil when mechanism OFFERED by server." + (string-match-p (rx-to-string + `(: (| bot ",") + ,(symbol-name + (alist-get 'mechanism erc-sasl--options)) + (| eot ","))) + (downcase offered))) + +(defun erc-sasl--add-hook () + (add-hook 'erc-server-AUTHENTICATE-functions + #'erc-sasl--authenticate-handler 0 t)) + +(defun erc-sasl--remove-hook () + (remove-hook 'erc-server-AUTHENTICATE-functions + #'erc-sasl--authenticate-handler t)) + +(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") + (s904 . "ERR_SASLFAIL (authentication failed) %s") + (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"))) + +(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." + ((unless erc--target + (erc-sasl--add-hook) + (erc-sasl--init) + (let* ((mech (alist-get 'mechanism erc-sasl--options)) + (client (erc-sasl--create-client mech))) + (unless client + (erc-display-error-notice nil (format "Unknown mechanism: %s" mech)) + (erc-error "Unknown mechanism: %s" mech)) + (setf (erc-sasl--state-client erc-sasl--state) client)))) + ((erc-sasl--remove-hook) + (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) + +;; FIXME do something decisive here +(define-erc-response-handler (902) + "Handle a ERR_NICKLOCKED response." nil + (let ((nick (car (erc-response.command-args parsed))) + (msg (erc-response.contents parsed))) + (erc-display-message parsed '(notice error) 'active 's902 ?n nick ?s msg))) + +(define-erc-response-handler (903) + "Handle a RPL_SASLSUCCESS response." nil + (when erc-sasl-mode + (unless erc-server-connected + (erc-server-send "CAP END"))) + (erc-handle-unknown-server-response proc parsed)) + +(define-erc-response-handler (904 905 906 907 908) + "Handle various SASL-related error responses." nil + (let* ((msg (intern (format "s%s" (erc-response.command parsed)))) + (args `(parsed (notice error) active ,msg + ,@(when (string= "908" (erc-response.command parsed)) + (list '?m + (alist-get 'mechanism erc-sasl--options))) + ?s ,(erc-response.contents parsed)))) + (apply #'erc-display-message args)) + (when (member (erc-response.command parsed) '("904" "905" "906")) + (run-hook-with-args 'erc-quit-hook proc) + (delete-process proc) + (erc-error "Disconnected from %s; please review SASL settings" proc))) + +(cl-defmethod erc--register-connection (&context (erc-sasl-mode (eql t))) + "Send speculative/pipelined CAP and AUTHENTICATE and hope for the best." + (erc-server-send "CAP REQ :sasl") + (erc-login) + (let* ((c (erc-sasl--state-client erc-sasl--state)) + (m (sasl-mechanism-name (sasl-client-mechanism c)))) + (erc-server-send (format "AUTHENTICATE %s" m)))) + +(provide 'erc-sasl) +;;; erc-sasl.el ends here +;; +;; Local Variables: +;; generated-autoload-file: "erc-loaddefs.el" +;; End: diff --git a/test/lisp/erc/erc-sasl-tests.el b/test/lisp/erc/erc-sasl-tests.el new file mode 100644 index 0000000000..beac287a6e --- /dev/null +++ b/test/lisp/erc/erc-sasl-tests.el @@ -0,0 +1,299 @@ +;;; erc-sasl-tests.el --- Tests for erc-sasl. -*- lexical-binding:t -*- + +;; Copyright (C) 2020-2022 Free Software Foundation, Inc. +;; +;; This file is part of GNU Emacs. +;; +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. +;; +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. +;; +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see . + +;;; Commentary: + +;;; Code: + +(require 'ert-x) +(require 'erc-sasl) + +(ert-deftest erc-sasl--mechanism-offered-p () + (let ((erc-sasl--options '((mechanism . external)))) + (should (erc-sasl--mechanism-offered-p "foo,external")) + (should (erc-sasl--mechanism-offered-p "external,bar")) + (should (erc-sasl--mechanism-offered-p "foo,external,bar")) + (should-not (erc-sasl--mechanism-offered-p "fooexternal")) + (should-not (erc-sasl--mechanism-offered-p "externalbar")))) + +(ert-deftest erc-sasl--read-password () + (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"))) + (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")))) + + (let* ((entries (list + "machine GNU/chat port 6697 user bob password spam" + "machine FSF.chat port 6697 user bob password sesame" + "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-sasl-auth-source-function #'erc-auth-source-search) + erc-server-announced-name ; too early + auth-source-do-cache) + + (unwind-protect + (ert-info ("Auth source") + + (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 ("Use session ID when password empty") + (let ((erc-sasl--options '((user . "bob") (password))) + (erc-networks--id (erc-networks--id-create 'GNU/chat))) + (should (string= (erc-sasl--read-password nil) "spam"))))) + + (delete-file netrc-file)) + + (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-deftest erc-sasl-create-client--plain () + (let* ((erc-session-password "password123") + (erc-server-current-nick "tester") + (erc-session-port 1667) + (erc-session-server "localhost") + (client (erc-sasl--create-client 'plain)) + (result (sasl-next-step client nil))) + (should (equal (format "%S" [erc-sasl--plain-response + "\0tester\0password123"]) + (format "%S" result))) + (should (string= (sasl-step-data result) "\0tester\0password123")) + (should-not (sasl-next-step client result))) + (should (equal (assoc-default "PLAIN" sasl-mechanism-alist) '(sasl-plain)))) + +(ert-deftest erc-sasl-create-client--external () + (let* ((erc-server-current-nick "tester") + (client (erc-sasl--create-client 'external)) + (result (sasl-next-step client nil))) + (should (equal (format "%S" [ignore nil]) (format "%S" result))) + (should-not (sasl-step-data result)) + (should-not (sasl-next-step client result))) + (should-not (member "EXTERNAL" sasl-mechanisms)) + (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"))) + (mock-rvs (list "c5RqLCZy0L4fGkKAZ0hujFBs" "")) + (sasl-unique-id-function (lambda () (pop mock-rvs))) + (client (erc-sasl--create-client 'scram-sha-1)) + (step (sasl-next-step client nil))) + (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 + ,req]) + (format "%S" step))) + (should (string= (sasl-step-data step) req)))) + (ert-info ("Server's initial response") + (let ((resp (concat "r=c5RqLCZy0L4fGkKAZ0hujFBsXQoKcivqCw9iDZPSpb," + "s=5mJO6d4rjCnsBU1X," + "i=4096")) + (req (concat "c=bixhPWppbGxlcyw=," + "r=c5RqLCZy0L4fGkKAZ0hujFBsXQoKcivqCw9iDZPSpb," + "p=OVUhgPu8wEm2cDoVLfaHzVUYPWU="))) + (sasl-step-set-data step resp) + (setq step (sasl-next-step client step)) + (should (equal (format "%S" + `[erc-sasl--scram-sha-1-client-final-message + ,req]) + (format "%S" step))) + (should (string= (sasl-step-data step) req)))) + (ert-info ("Server's final message") + (let ((resp "v=ZWR23c9MJir0ZgfGf5jEtLOn6Ng=")) + (sasl-step-set-data step resp) + (setq step (sasl-next-step client step)) + (should-not (sasl-step-data step))))) + (should (eq sasl-unique-id-function #'sasl-unique-id-function))) + +(ert-deftest erc-sasl-create-client--scram-sha-256 () + (unless (featurep 'sasl-scram-sha256) + (ert-skip "Emacs lacks sasl-scram-sha256")) + (let* ((erc-server-current-nick "jilles") + (erc-session-password "sesame") + (erc-sasl--options '((authzid . "jilles"))) + (mock-rvs (list "c5RqLCZy0L4fGkKAZ0hujFBs" "")) + (sasl-unique-id-function (lambda () (pop mock-rvs))) + (client (erc-sasl--create-client 'scram-sha-256)) + (step (sasl-next-step client nil))) + (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 + ,req]) + (format "%S" step))) + (should (string= (sasl-step-data step) req)))) + (ert-info ("Server's initial response") + (let ((resp (concat + "r=c5RqLCZy0L4fGkKAZ0hujFBse697140729d8445fb95ec94ceacb14b3," + "s=MTk2M2VkMzM5ZmU0NDRiYmI0MzIyOGVhN2YwNzYwNmI=," + "i=4096")) + (req (concat + "c=bixhPWppbGxlcyw=," + "r=c5RqLCZy0L4fGkKAZ0hujFBse697140729d8445fb95ec94ceacb14b3," + "p=1vDesVBzJmv0lX0Ae1kHFtdVHkC6j4gISKVqaR45HFg="))) + (sasl-step-set-data step resp) + (setq step (sasl-next-step client step)) + (should (equal (format "%S" + `[erc-sasl--scram-sha-256-client-final-message + ,req]) + (format "%S" step))) + (should (string= (sasl-step-data step) req)))) + (ert-info ("Server's final message") + (let ((resp "v=gUePTYSZN9xgcE06KSyKO9fUmSwH26qifoapXyEs75s=")) + (sasl-step-set-data step resp) + (setq step (sasl-next-step client step)) + (should-not (sasl-step-data step))))) + (should (eq sasl-unique-id-function #'sasl-unique-id-function))) + +(ert-deftest erc-sasl-create-client--scram-sha-256--no-authzid () + (unless (featurep 'sasl-scram-sha256) + (ert-skip "Emacs lacks sasl-scram-sha256")) + (let* ((erc-server-current-nick "jilles") + (erc-session-password "sesame") + (mock-rvs (list "c5RqLCZy0L4fGkKAZ0hujFBs" "")) + (sasl-unique-id-function (lambda () (pop mock-rvs))) + (client (erc-sasl--create-client 'scram-sha-256)) + (step (sasl-next-step client nil))) + (ert-info ("Client's initial request") + (let ((req "n,,n=jilles,r=c5RqLCZy0L4fGkKAZ0hujFBs")) + (should (equal (format "%S" + `[erc-compat--sasl-scram-client-first-message + ,req]) + (format "%S" step))) + (should (string= (sasl-step-data step) req)))) + (ert-info ("Server's initial response") + (let ((resp (concat + "r=c5RqLCZy0L4fGkKAZ0hujFBsd4067f0afdb54c3dbd4fe645b84cae37," + "s=ZTg1MmE1YmFhZGI1NDcyMjk3NzYwZmRjZDM3Y2I1OTM=," + "i=4096")) + (req (concat + "c=biws," + "r=c5RqLCZy0L4fGkKAZ0hujFBsd4067f0afdb54c3dbd4fe645b84cae37," + "p=LP4sjJrjJKp5qTsARyZCppXpKLu4FMM284hNESPvGhI="))) + (sasl-step-set-data step resp) + (setq step (sasl-next-step client step)) + (should (equal (format "%S" + `[erc-sasl--scram-sha-256-client-final-message + ,req]) + (format "%S" step))) + (should (string= (sasl-step-data step) req)))) + (ert-info ("Server's final message") + (let ((resp "v=847WXfnmReGyE1qlq1And6R4bPBNROTZ7EMS/QrJtUM=")) + (sasl-step-set-data step resp) + (setq step (sasl-next-step client step)) + (should-not (sasl-step-data step))))) + (should (eq sasl-unique-id-function #'sasl-unique-id-function))) + +(ert-deftest erc-sasl-create-client--scram-sha-512--no-authzid () + (unless (featurep 'sasl-scram-sha256) + (ert-skip "Emacs lacks sasl-scram-sha512")) + (let* ((erc-server-current-nick "jilles") + (erc-session-password "sesame") + (mock-rvs (list "c5RqLCZy0L4fGkKAZ0hujFBs" "")) + (sasl-unique-id-function (lambda () (pop mock-rvs))) + (client (erc-sasl--create-client 'scram-sha-512)) + (step (sasl-next-step client nil))) + (ert-info ("Client's initial request") + (let ((req "n,,n=jilles,r=c5RqLCZy0L4fGkKAZ0hujFBs")) + (should (equal (format "%S" + `[erc-compat--sasl-scram-client-first-message + ,req]) + (format "%S" step))) + (should (string= (sasl-step-data step) req)))) + (ert-info ("Server's initial response") + (let ((resp (concat + "r=c5RqLCZy0L4fGkKAZ0hujFBs54c592745ce14e559fcc3f27b15464f6," + "s=YzMzOWZiY2U0YzcwNDA0M2I4ZGE2M2ZjOTBjODExZTM=," + "i=4096")) + (req (concat + "c=biws," + "r=c5RqLCZy0L4fGkKAZ0hujFBs54c592745ce14e559fcc3f27b15464f6," + "p=vMBb9tKxFAfBtel087/GLbo4objAIYr1wM+mFv/jYLKXE" + "NUF0vynm81qQbywQE5ScqFFdAfwYMZq/lj4s0V1OA=="))) + (sasl-step-set-data step resp) + (setq step (sasl-next-step client step)) + (should (equal (format + "%S" `[erc-sasl--scram-sha-512-client-final-message + ,req]) + (format "%S" step))) + (should (string= (sasl-step-data step) req)))) + (ert-info ("Server's final message") + (let ((resp (concat "v=Va7NIvt8wCdhvxnv+bZriSxGoto6On5EVnRHO/ece8zs0" + "qpQassdqir1Zlwh3e3EmBq+kcSy+ClNCsbzBpXe/w=="))) + (sasl-step-set-data step resp) + (setq step (sasl-next-step client step)) + (should-not (sasl-step-data step))))) + (should (eq sasl-unique-id-function #'sasl-unique-id-function))) + +(defconst erc-sasl-tests-ecdsa-key-file " +-----BEGIN EC PARAMETERS----- +BggqhkjOPQMBBw== +-----END EC PARAMETERS----- +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIIJueQ3W2IrGbe9wKdOI75yGS7PYZSj6W4tg854hlsvmoAoGCCqGSM49 +AwEHoUQDQgAEAZmaVhNSMmV5r8FXPvKuMnqDKyIA9pDHN5TNMfiF3mMeikGgK10W +IRX9cyi2wdYg9mUUYyh9GKdBCYHGUJAiCA== +-----END EC PRIVATE KEY----- +") + +(ert-deftest erc-sasl-create-client-ecdsa () + (unless (executable-find "openssl") + (ert-skip "System lacks openssl")) + (let* ((erc-server-current-nick "jilles") + (keyfile (make-temp-file "ecdsa_key.pem" nil nil + erc-sasl-tests-ecdsa-key-file)) + (erc-sasl--options `((ecdsa-private-key . ,keyfile))) + (client (erc-sasl--create-client 'ecdsa-nist256p-challenge)) + (step (sasl-next-step client nil))) + (ert-info ("Client's initial request") + (should (equal (format "%S" [erc-sasl--ecdsa-first "jilles"]) + (format "%S" step))) + (should (string= (sasl-step-data step) "jilles"))) + (ert-info ("Server's initial response") + (let ((resp (concat "\0\1\2\3\4\5\6\7\10\11\12\13\14\15\16\17\20" + "\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)))) + (should-not (sasl-next-step client step)) + (delete-file keyfile))) + +;;; erc-sasl-tests.el ends here diff --git a/test/lisp/erc/erc-scenarios-sasl.el b/test/lisp/erc/erc-scenarios-sasl.el new file mode 100644 index 0000000000..3ff7cc805d --- /dev/null +++ b/test/lisp/erc/erc-scenarios-sasl.el @@ -0,0 +1,161 @@ +;;; erc-scenarios-sasl.el --- SASL tests for ERC -*- lexical-binding: t -*- + +;; Copyright (C) 2022 Free Software Foundation, Inc. +;; +;; This file is part of GNU Emacs. +;; +;; This program is free software: you can redistribute it and/or +;; modify it under the terms of the GNU General Public License as +;; published by the Free Software Foundation, either version 3 of the +;; License, or (at your option) any later version. +;; +;; This program is distributed in the hope that it will be useful, but +;; WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +;; General Public License for more details. +;; +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see +;; . + +;;; Code: + +(require 'ert-x) +(eval-and-compile + (let ((load-path (cons (ert-resource-directory) load-path))) + (require 'erc-scenarios-common))) + +(declare-function sasl-client-name "sasl" (client)) + +(require 'erc-scenarios-common) +(require 'erc-sasl) + +(ert-deftest erc-scenarios-sasl--plain () + :tags '(:expensive-test) + (erc-scenarios-common-with-cleanup + ((erc-scenarios-common-dialog "sasl") + (erc-d-linger-secs 0.5) + (erc-server-flood-penalty 0.1) + (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") + (inhibit-message noninteractive) + (expect (erc-d-t-make-expecter))) + + (ert-info ("Connect") + (with-current-buffer (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))))) + + (erc-d-t-wait-for 10 "server buffer ready" (get-buffer "ExampleOrg")) + + (ert-info ("Notices received") + (with-current-buffer "ExampleOrg" + (funcall expect 10 "This server is in debug mode") + ;; Regression "\0\0\0\0 ..." caused by (fillarray passphrase 0) + (should (string= erc-sasl-password "password123")))))) + +(ert-deftest erc-scenarios-sasl--external () + :tags '(:expensive-test) + (erc-scenarios-common-with-cleanup + ((erc-scenarios-common-dialog "sasl") + (erc-d-linger-secs 0.5) + (erc-server-flood-penalty 0.1) + (dumb-server (erc-d-run "localhost" t 'external)) + (port (process-contact dumb-server :service)) + (erc-modules (cons 'sasl erc-modules)) + (erc-sasl-mechanism 'external) + (inhibit-message noninteractive) + (expect (erc-d-t-make-expecter))) + + (ert-info ("Connect") + (with-current-buffer (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))))) + + (erc-d-t-wait-for 10 "server buffer ready" (get-buffer "ExampleOrg")) + + (ert-info ("Notices received") + (with-current-buffer "ExampleOrg" + (funcall expect 10 "903 * Authentication successful") + (funcall expect 10 "This server is in debug mode"))))) + +(ert-deftest erc-scenarios-sasl--plain-fail () + :tags '(:expensive-test) + (erc-scenarios-common-with-cleanup + ((erc-scenarios-common-dialog "sasl") + (erc-d-linger-secs 0.5) + (erc-server-flood-penalty 0.1) + (dumb-server (erc-d-run "localhost" t 'plain-failed)) + (port (process-contact dumb-server :service)) + (erc-modules (cons 'sasl erc-modules)) + (erc-sasl-password "wrong") + (erc-sasl-mechanism 'plain) + (inhibit-message noninteractive) + (expect (erc-d-t-make-expecter)) + (buf nil)) + + (ert-info ("Connect") + (setq buf (erc :server "127.0.0.1" + :port port + :nick "tester" + :user "tester" + :full-name "tester")) + (let ((err (should-error + (with-current-buffer buf + (funcall expect 20 "Connection failed!"))))) + (should (string-search "please review" (cadr err))) + (with-current-buffer buf + (funcall expect 10 "Opening connection") + (funcall expect 20 "SASL authentication failed") + (should-not (erc-server-process-alive))))))) + +(defun erc-scenarios--common--sasl (mech) + (erc-scenarios-common-with-cleanup + ((erc-scenarios-common-dialog "sasl") + (erc-d-linger-secs 0.5) + (erc-server-flood-penalty 0.1) + (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-mechanism mech) + (mock-rvs (list "c5RqLCZy0L4fGkKAZ0hujFBs" "")) + (sasl-unique-id-function (lambda () (pop mock-rvs))) + (inhibit-message noninteractive) + (expect (erc-d-t-make-expecter))) + + (ert-info ("Connect") + (with-current-buffer (erc :server "127.0.0.1" + :port port + :nick "jilles" + :full-name "jilles") + (should (string= (buffer-name) (format "127.0.0.1:%d" port))))) + + (erc-d-t-wait-for 10 "server buffer ready" (get-buffer "jaguar")) + + (ert-info ("Notices received") + (with-current-buffer "jaguar" + (funcall expect 10 "Found your hostname") + (funcall expect 20 "marked as being away"))))) + +(ert-deftest erc-scenarios-sasl--scram-sha-1 () + :tags '(:expensive-test) + (let ((erc-sasl-authzid "jilles")) + (erc-scenarios--common--sasl 'scram-sha-1))) + +(ert-deftest erc-scenarios-sasl--scram-sha-256 () + :tags '(:expensive-test) + (unless (featurep 'sasl-scram-sha256) + (ert-skip "Emacs lacks sasl-scram-sha256")) + (erc-scenarios--common--sasl 'scram-sha-256)) + +;;; erc-scenarios-sasl.el ends here diff --git a/test/lisp/erc/resources/sasl/external.eld b/test/lisp/erc/resources/sasl/external.eld new file mode 100644 index 0000000000..2cd237ec4d --- /dev/null +++ b/test/lisp/erc/resources/sasl/external.eld @@ -0,0 +1,33 @@ +;; -*- mode: lisp-data; -*- +((cap-req 10 "CAP REQ :sasl")) +((nick 1 "NICK tester")) +((user 1 "USER tester 0 * :tester")) + +((auth-req 3.2 "AUTHENTICATE EXTERNAL") + (0.0 ":irc.example.org CAP * ACK :sasl") + (0.0 "AUTHENTICATE +")) + +((auth-noop 3.2 "AUTHENTICATE +") + (0.0 ":irc.example.org 900 * * tester :You are now logged in as tester") + (0.0 ":irc.example.org 903 * :Authentication successful")) + +((cap-end 3.2 "CAP END") + (0.0 ":irc.example.org 001 tester :Welcome to the ExampleOrg IRC Network tester") + (0.01 ":irc.example.org 002 tester :Your host is irc.example.org, running version oragono-2.6.1") + (0.01 ":irc.example.org 003 tester :This server was created Sat, 17 Jul 2021 09:06:42 UTC") + (0.01 ":irc.example.org 004 tester irc.example.org oragono-2.6.1 BERTZios CEIMRUabefhiklmnoqstuv Iabefhkloqv") + (0.0 ":irc.example.org 005 tester AWAYLEN=200 BOT=B CASEMAPPING=ascii CHANLIMIT=#:100 CHANMODES=Ibe,k,fl,CEMRUimnstu CHANNELLEN=64 CHANTYPES=# ELIST=U EXCEPTS EXTBAN=,m FORWARD=f INVEX KICKLEN=390 :are supported by this server") + (0.01 ":irc.example.org 005 tester MAXLIST=beI:60 MAXTARGETS=4 MODES MONITOR=100 NETWORK=ExampleOrg NICKLEN=32 PREFIX=(qaohv)~&@%+ STATUSMSG=~&@%+ TARGMAX=NAMES:1,LIST:1,KICK:1,WHOIS:1,USERHOST:10,PRIVMSG:4,TAGMSG:4,NOTICE:4,MONITOR:100 TOPICLEN=390 UTF8MAPPING=rfc8265 UTF8ONLY :are supported by this server") + (0.01 ":irc.example.org 005 tester draft/CHATHISTORY=100 :are supported by this server") + (0.0 ":irc.example.org 251 tester :There are 1 users and 0 invisible on 1 server(s)") + (0.0 ":irc.example.org 252 tester 0 :IRC Operators online") + (0.0 ":irc.example.org 253 tester 0 :unregistered connections") + (0.0 ":irc.example.org 254 tester 0 :channels formed") + (0.0 ":irc.example.org 255 tester :I have 1 clients and 0 servers") + (0.0 ":irc.example.org 265 tester 1 1 :Current local users 1, max 1") + (0.21 ":irc.example.org 266 tester 1 1 :Current global users 1, max 1") + (0.0 ":irc.example.org 422 tester :MOTD File is missing")) + +((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.")) diff --git a/test/lisp/erc/resources/sasl/plain-failed.eld b/test/lisp/erc/resources/sasl/plain-failed.eld new file mode 100644 index 0000000000..336700290c --- /dev/null +++ b/test/lisp/erc/resources/sasl/plain-failed.eld @@ -0,0 +1,16 @@ +;; -*- mode: lisp-data; -*- +((cap-req 10 "CAP REQ :sasl")) +((nick 1 "NICK tester")) +((user 1 "USER tester 0 * :tester") + (0.0 ":irc.foonet.org NOTICE * :*** Looking up your hostname...") + (0.0 ":irc.foonet.org NOTICE * :*** Found your hostname") + (0.0 ":irc.foonet.org CAP * ACK :cap-notify sasl")) + +((authenticate-plain 3.2 "AUTHENTICATE PLAIN") + (0.0 ":irc.foonet.org AUTHENTICATE +")) + +((authenticate-gimme 3.2 "AUTHENTICATE AHRlc3RlcgB3cm9uZw==") + (0.0 ":irc.foonet.org 900 * * tester :You are now logged in as tester") + (0.0 ":irc.foonet.org 904 * :SASL authentication failed: Invalid account credentials")) + +((cap-end 3.2 "CAP END")) diff --git a/test/lisp/erc/resources/sasl/plain.eld b/test/lisp/erc/resources/sasl/plain.eld new file mode 100644 index 0000000000..9c6ce3feeb --- /dev/null +++ b/test/lisp/erc/resources/sasl/plain.eld @@ -0,0 +1,35 @@ +;; -*- mode: lisp-data; -*- +((cap-req 10 "CAP REQ :sasl")) +((nick 1 "NICK tester")) +((user 1 "USER tester 0 * :tester") + (0.0 ":irc.example.org NOTICE * :*** Looking up your hostname...") + (0.0 ":irc.example.org NOTICE * :*** Found your hostname") + (0.0 ":irc.example.org CAP * ACK :sasl")) + +((authenticate-plain 3.2 "AUTHENTICATE PLAIN") + (0.0 ":irc.example.org AUTHENTICATE +")) + +((authenticate-gimme 3.2 "AUTHENTICATE AHRlc3RlcgBwYXNzd29yZDEyMw==") + (0.0 ":irc.example.org 900 * * tester :You are now logged in as tester") + (0.0 ":irc.example.org 903 * :Authentication successful")) + +((cap-end 3.2 "CAP END") + (0.0 ":irc.example.org 001 tester :Welcome to the ExampleOrg IRC Network tester") + (0.01 ":irc.example.org 002 tester :Your host is irc.example.org, running version oragono-2.6.1") + (0.01 ":irc.example.org 003 tester :This server was created Sat, 17 Jul 2021 09:06:42 UTC") + (0.01 ":irc.example.org 004 tester irc.example.org oragono-2.6.1 BERTZios CEIMRUabefhiklmnoqstuv Iabefhkloqv") + (0.0 ":irc.example.org 005 tester AWAYLEN=200 BOT=B CASEMAPPING=ascii CHANLIMIT=#:100 CHANMODES=Ibe,k,fl,CEMRUimnstu CHANNELLEN=64 CHANTYPES=# ELIST=U EXCEPTS EXTBAN=,m FORWARD=f INVEX KICKLEN=390 :are supported by this server") + (0.01 ":irc.example.org 005 tester MAXLIST=beI:60 MAXTARGETS=4 MODES MONITOR=100 NETWORK=ExampleOrg NICKLEN=32 PREFIX=(qaohv)~&@%+ STATUSMSG=~&@%+ TARGMAX=NAMES:1,LIST:1,KICK:1,WHOIS:1,USERHOST:10,PRIVMSG:4,TAGMSG:4,NOTICE:4,MONITOR:100 TOPICLEN=390 UTF8MAPPING=rfc8265 UTF8ONLY :are supported by this server") + (0.01 ":irc.example.org 005 tester draft/CHATHISTORY=100 :are supported by this server") + (0.0 ":irc.example.org 251 tester :There are 1 users and 0 invisible on 1 server(s)") + (0.0 ":irc.example.org 252 tester 0 :IRC Operators online") + (0.0 ":irc.example.org 253 tester 0 :unregistered connections") + (0.0 ":irc.example.org 254 tester 0 :channels formed") + (0.0 ":irc.example.org 255 tester :I have 1 clients and 0 servers") + (0.0 ":irc.example.org 265 tester 1 1 :Current local users 1, max 1") + (0.21 ":irc.example.org 266 tester 1 1 :Current global users 1, max 1") + (0.0 ":irc.example.org 422 tester :MOTD File is missing")) + +((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.")) diff --git a/test/lisp/erc/resources/sasl/scram-sha-1.eld b/test/lisp/erc/resources/sasl/scram-sha-1.eld new file mode 100644 index 0000000000..49980e9e12 --- /dev/null +++ b/test/lisp/erc/resources/sasl/scram-sha-1.eld @@ -0,0 +1,47 @@ +;;; -*- mode: lisp-data -*- +((cap-req 5.2 "CAP REQ :sasl")) +((nick 10 "NICK jilles")) +((user 10 "USER user 0 * :jilles") + (0 "NOTICE AUTH :*** Processing connection to jaguar.test") + (0 "NOTICE AUTH :*** Looking up your hostname...") + (0 "NOTICE AUTH :*** Checking Ident") + (0 "NOTICE AUTH :*** No Ident response") + (0 "NOTICE AUTH :*** Found your hostname") + (0 ":jaguar.test CAP jilles ACK :sasl")) + +((auth-init 10 "AUTHENTICATE SCRAM-SHA-1") + (0 "AUTHENTICATE +")) + +((auth-challenge 10 "AUTHENTICATE bixhPWppbGxlcyxuPWppbGxlcyxyPWM1UnFMQ1p5MEw0ZkdrS0FaMGh1akZCcw==") + (0 "AUTHENTICATE cj1jNVJxTENaeTBMNGZHa0tBWjBodWpGQnNYUW9LY2l2cUN3OWlEWlBTcGIscz01bUpPNmQ0cmpDbnNCVTFYLGk9NDA5Ng==")) + +((auth-final 10 "AUTHENTICATE Yz1iaXhoUFdwcGJHeGxjeXc9LHI9YzVScUxDWnkwTDRmR2tLQVowaHVqRkJzWFFvS2NpdnFDdzlpRFpQU3BiLHA9T1ZVaGdQdTh3RW0yY0RvVkxmYUh6VlVZUFdVPQ==") + (0 "AUTHENTICATE dj1aV1IyM2M5TUppcjBaZ2ZHZjVqRXRMT242Tmc9")) + +((auth-done 10 "AUTHENTICATE +") + (0 ":jaguar.test 900 jilles jilles!jilles@localhost.stack.nl jilles :You are now logged in as jilles") + (0 ":jaguar.test 903 jilles :SASL authentication successful")) + +((cap-end 10.2 "CAP END") + (0 ":jaguar.test 001 jilles :Welcome to the jaguar IRC Network jilles!~jilles@127.0.0.1") + (0 ":jaguar.test 002 jilles :Your host is jaguar.test, running version InspIRCd-3") + (0 ":jaguar.test 003 jilles :This server was created 09:44:05 Dec 24 2020") + (0 ":jaguar.test 004 jilles jaguar.test InspIRCd-3 BILRSWcghiorswz ABEFHIJLMNOQRSTXYabcefghijklmnopqrstuvz :BEFHIJLXYabefghjkloqv") + (0 ":jaguar.test 005 jilles ACCEPT=30 AWAYLEN=200 BOT=B CALLERID=g CASEMAPPING=rfc1459 CHANLIMIT=#:120 CHANMODES=IXbeg,k,BEFHJLfjl,AMNOQRSTcimnprstuz CHANNELLEN=64 CHANTYPES=# ELIST=CMNTU ESILENCE=CcdiNnPpTtx EXCEPTS=e :are supported by this server") + (0 ":jaguar.test 005 jilles EXTBAN=,ANOQRSTUacmnprz HOSTLEN=64 INVEX=I KEYLEN=32 KICKLEN=255 LINELEN=512 MAXLIST=I:100,X:100,b:100,e:100,g:100 MAXTARGETS=20 MODES=20 MONITOR=30 NAMELEN=128 NAMESX NETWORK=jaguar :are supported by this server") + (0 ":jaguar.test 005 jilles NICKLEN=31 PREFIX=(Yqaohv)!~&@%+ REMOVE SAFELIST SECURELIST=60 SILENCE=32 STATUSMSG=!~&@%+ TOPICLEN=307 UHNAMES USERIP USERLEN=11 USERMODES=,,s,BILRSWcghiorwz WATCH=30 :are supported by this server") + (0 ":jaguar.test 005 jilles :are supported by this server") + (0 ":jaguar.test 251 jilles :There are 740 users and 108 invisible on 11 servers") + (0 ":jaguar.test 252 jilles 10 :operator(s) online") + (0 ":jaguar.test 254 jilles 373 :channels formed") + (0 ":jaguar.test 255 jilles :I have 28 clients and 1 servers") + (0 ":jaguar.test 265 jilles :Current local users: 28 Max: 29") + (0 ":jaguar.test 266 jilles :Current global users: 848 Max: 879") + (0 ":jaguar.test 375 jilles :jaguar.test message of the day") + (0 ":jaguar.test 372 jilles : ~~ some message of the day ~~") + (0 ":jaguar.test 372 jilles : ~~ or rkpryyrag gb rnpu bgure ~~") + (0 ":jaguar.test 376 jilles :End of message of the day.")) + +((mode-user 1.2 "MODE jilles +i") + (0 ":jilles!~jilles@127.0.0.1 MODE jilles :+ri") + (0 ":jaguar.test 306 jilles :You have been marked as being away")) diff --git a/test/lisp/erc/resources/sasl/scram-sha-256.eld b/test/lisp/erc/resources/sasl/scram-sha-256.eld new file mode 100644 index 0000000000..74de9a23ec --- /dev/null +++ b/test/lisp/erc/resources/sasl/scram-sha-256.eld @@ -0,0 +1,47 @@ +;;; -*- mode: lisp-data -*- +((cap-req 5.2 "CAP REQ :sasl")) +((nick 10 "NICK jilles")) +((user 10 "USER user 0 * :jilles") + (0 "NOTICE AUTH :*** Processing connection to jaguar.test") + (0 "NOTICE AUTH :*** Looking up your hostname...") + (0 "NOTICE AUTH :*** Checking Ident") + (0 "NOTICE AUTH :*** No Ident response") + (0 "NOTICE AUTH :*** Found your hostname") + (0 ":jaguar.test CAP jilles ACK :sasl")) + +((auth-init 10 "AUTHENTICATE SCRAM-SHA-256") + (0 "AUTHENTICATE +")) + +((auth-challenge 10 "AUTHENTICATE biwsbj1qaWxsZXMscj1jNVJxTENaeTBMNGZHa0tBWjBodWpGQnM=") + (0 "AUTHENTICATE cj1jNVJxTENaeTBMNGZHa0tBWjBodWpGQnNkNDA2N2YwYWZkYjU0YzNkYmQ0ZmU2NDViODRjYWUzNyxzPVpUZzFNbUUxWW1GaFpHSTFORGN5TWprM056WXdabVJqWkRNM1kySTFPVE09LGk9NDA5Ng==")) + +((auth-final 10 "AUTHENTICATE Yz1iaXdzLHI9YzVScUxDWnkwTDRmR2tLQVowaHVqRkJzZDQwNjdmMGFmZGI1NGMzZGJkNGZlNjQ1Yjg0Y2FlMzcscD1MUDRzakpyakpLcDVxVHNBUnlaQ3BwWHBLTHU0Rk1NMjg0aE5FU1B2R2hJPQ==") + (0 "AUTHENTICATE dj04NDdXWGZubVJlR3lFMXFscTFBbmQ2UjRiUEJOUk9UWjdFTVMvUXJKdFVNPQ==")) + +((auth-done 10 "AUTHENTICATE +") + (0 ":jaguar.test 900 jilles jilles!jilles@localhost.stack.nl jilles :You are now logged in as jilles") + (0 ":jaguar.test 903 jilles :SASL authentication successful")) + +((cap-end 10.2 "CAP END") + (0 ":jaguar.test 001 jilles :Welcome to the jaguar IRC Network jilles!~jilles@127.0.0.1") + (0 ":jaguar.test 002 jilles :Your host is jaguar.test, running version InspIRCd-3") + (0 ":jaguar.test 003 jilles :This server was created 09:44:05 Dec 24 2020") + (0 ":jaguar.test 004 jilles jaguar.test InspIRCd-3 BILRSWcghiorswz ABEFHIJLMNOQRSTXYabcefghijklmnopqrstuvz :BEFHIJLXYabefghjkloqv") + (0 ":jaguar.test 005 jilles ACCEPT=30 AWAYLEN=200 BOT=B CALLERID=g CASEMAPPING=rfc1459 CHANLIMIT=#:120 CHANMODES=IXbeg,k,BEFHJLfjl,AMNOQRSTcimnprstuz CHANNELLEN=64 CHANTYPES=# ELIST=CMNTU ESILENCE=CcdiNnPpTtx EXCEPTS=e :are supported by this server") + (0 ":jaguar.test 005 jilles EXTBAN=,ANOQRSTUacmnprz HOSTLEN=64 INVEX=I KEYLEN=32 KICKLEN=255 LINELEN=512 MAXLIST=I:100,X:100,b:100,e:100,g:100 MAXTARGETS=20 MODES=20 MONITOR=30 NAMELEN=128 NAMESX NETWORK=jaguar :are supported by this server") + (0 ":jaguar.test 005 jilles NICKLEN=31 PREFIX=(Yqaohv)!~&@%+ REMOVE SAFELIST SECURELIST=60 SILENCE=32 STATUSMSG=!~&@%+ TOPICLEN=307 UHNAMES USERIP USERLEN=11 USERMODES=,,s,BILRSWcghiorwz WATCH=30 :are supported by this server") + (0 ":jaguar.test 005 jilles :are supported by this server") + (0 ":jaguar.test 251 jilles :There are 740 users and 108 invisible on 11 servers") + (0 ":jaguar.test 252 jilles 10 :operator(s) online") + (0 ":jaguar.test 254 jilles 373 :channels formed") + (0 ":jaguar.test 255 jilles :I have 28 clients and 1 servers") + (0 ":jaguar.test 265 jilles :Current local users: 28 Max: 29") + (0 ":jaguar.test 266 jilles :Current global users: 848 Max: 879") + (0 ":jaguar.test 375 jilles :jaguar.test message of the day") + (0 ":jaguar.test 372 jilles : ~~ some message of the day ~~") + (0 ":jaguar.test 372 jilles : ~~ or rkpryyrag gb rnpu bgure ~~") + (0 ":jaguar.test 376 jilles :End of message of the day.")) + +((mode-user 1.2 "MODE jilles +i") + (0 ":jilles!~jilles@127.0.0.1 MODE jilles :+ri") + (0 ":jaguar.test 306 jilles :You have been marked as being away")) -- 2.37.2