From 811c7e70b55e7aeace792ffbf19203ef36f30bd2 Mon Sep 17 00:00:00 2001 From: Stefan Kangas Date: Wed, 12 Oct 2022 17:29:04 +0200 Subject: [PATCH] Make `message-unique-id' less prone to collisions * lisp/gnus/message.el (message-unique-id): Make less prone to collisions. (Bug#58472) (message-number-base36): Make obsolete in favor of... (message--number-base62): ...this new function. (message-unique-id-char): Make unused variable obsolete. * test/lisp/gnus/message-tests.el (message-unique-id-test) (message-number-base62-test): New tests. --- lisp/gnus/message.el | 79 ++++++++++++++++----------------- test/lisp/gnus/message-tests.el | 10 +++++ 2 files changed, 49 insertions(+), 40 deletions(-) diff --git a/lisp/gnus/message.el b/lisp/gnus/message.el index a714e31876..1590a8b289 100644 --- a/lisp/gnus/message.el +++ b/lisp/gnus/message.el @@ -5886,50 +5886,36 @@ message-make-message-id "_-_" "")) "@" (message-make-fqdn) ">")) -(defvar message-unique-id-char nil) - -;; If you ever change this function, make sure the new version -;; cannot generate IDs that the old version could. -;; You might for example insert a "." somewhere (not next to another dot -;; or string boundary), or modify the "fsf" string. (defun message-unique-id () - ;; Don't use fractional seconds from timestamp; they may be unsupported. - ;; Instead we use this randomly inited counter. - (setq message-unique-id-char - ;; 2^16 * 25 just fits into 4 digits i base 36. - (let ((base (* 25 25))) - (if message-unique-id-char - (% (1+ message-unique-id-char) base) - (random base)))) - (let ((tm (time-convert nil 'integer))) + "Return a generated string suitable for using as a unique Message-ID." + (let ((random-num + (seq-reduce (lambda (a i) (+ (ash a 8) i)) + (secure-hash 'md5 'iv-auto 128 nil t) 0))) + ;; Use the PRNG to set an extra couple of bits, to avoid having + ;; every Message-Id start with "A-F". + (setq random-num (logior random-num (ash (random 16) 128))) (concat - (if (or (eq system-type 'ms-dos) - ;; message-number-base36 doesn't handle bigints. - (floatp (user-uid))) - (let ((user (downcase (user-login-name)))) - (while (string-match "[^a-z0-9_]" user) - (aset user (match-beginning 0) ?_)) - user) - (message-number-base36 (user-uid) -1)) - (message-number-base36 (+ (ash tm -16) - (ash (% message-unique-id-char 25) 16)) - 4) - (message-number-base36 (+ (logand tm #xffff) - (ash (/ message-unique-id-char 25) 16)) - 4) - ;; Append a given name, because while the generated ID is unique - ;; to this newsreader, other newsreaders might otherwise generate - ;; the same ID via another algorithm. - ".fsf"))) - -(defun message-number-base36 (num len) + ;; We used to base this on time, euid, etc., but now just use 128 + ;; bits of random data. The main requirements are that collisions + ;; are unlikely, and that the alphabet is limited. Note that the + ;; random data will be securely generated by getrandom() in Gnulib. + (message--number-base62 random-num 22) + ;; Append ".gnu" to advertise that we're GNU Emacs. + ".gnu"))) + +(defun message--number-base62 (num len) + "Return a string similar to Base64, but include only alpha-numerics. +NUM is the number to convert. +LEN is the length of the string (or -1 for no limit)." (if (if (< len 0) - (<= num 0) - (= len 0)) + (<= num 0) + (= len 0)) "" - (concat (message-number-base36 (/ num 36) (1- len)) - (char-to-string (aref "zyxwvutsrqponmlkjihgfedcba9876543210" - (% num 36)))))) + (concat (message--number-base62 (/ num 62) (1- len)) + (char-to-string (aref (concat "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789") + (% num 62)))))) (defun message-make-organization () "Make an Organization header." @@ -8998,6 +8984,19 @@ message-mailto-1 (message-goto-body) (message-goto-subject)))) +(make-obsolete-variable 'message-unique-id-char nil "29.1") +(defvar message-unique-id-char nil) + +(defun message-number-base36 (num len) + (declare (obsolete message--number-base62 "29.1")) + (if (if (< len 0) + (<= num 0) + (= len 0)) + "" + (concat (message-number-base36 (/ num 36) (1- len)) + (char-to-string (aref "0123456789abcdefghijklmnopqrstuvwxyz" + (% num 36)))))) + (provide 'message) (make-obsolete-variable 'message-load-hook diff --git a/test/lisp/gnus/message-tests.el b/test/lisp/gnus/message-tests.el index a724428ecb..851e07b7ae 100644 --- a/test/lisp/gnus/message-tests.el +++ b/test/lisp/gnus/message-tests.el @@ -31,6 +31,16 @@ (require 'cl-lib) +(ert-deftest message-unique-id-test () + (should (stringp (message-unique-id))) + (should (= (length (message-unique-id)) 26)) + (should (string-match (rx ".gnu" eos) (message-unique-id)))) + +(ert-deftest message--number-base62-test () + (should (equal (message--number-base62 10 -1) "K")) + (should (equal (message--number-base62 1 2) "AB")) + (should (equal (message--number-base62 (expt 62 5) -1) "BAAAAA"))) + (ert-deftest message-mode-propertize () (with-temp-buffer (unwind-protect -- 2.35.1