diff options
| author | Kai Tetzlaff | 2022-02-28 11:08:07 +0100 |
|---|---|---|
| committer | Lars Ingebrigtsen | 2022-09-06 13:33:48 +0200 |
| commit | ae963e80a79f5a9184daabfc8197f211a39b136d (patch) | |
| tree | bea2e9347427e3be97f663c6eadd2164aa7f5535 | |
| parent | 46963d0bc9058caeb8241abe34b1552bd83e097d (diff) | |
| download | emacs-ae963e80a79f5a9184daabfc8197f211a39b136d.tar.gz emacs-ae963e80a79f5a9184daabfc8197f211a39b136d.zip | |
Fix (mostly multibyte) issues in sieve-manage.el (Bug#54154)
The managesieve protocol (s. RFC5804) requires support for (a sightly
restricted variant of) UTF-8 in script content and script names. This
commit fixes/improves the handling of multibyte characters.
In addition, `sieve-manage-getscript' now properly handles NO
responses from the server instead of inflooping.
There are also some logging improvements.
* lisp/net/sieve-manage.el
(sieve-manage--append-to-log):
(sieve-manage--message):
(sieve-manage--error):
(sieve-manage-encode):
(sieve-manage-decode):
(sieve-manage-no-p): New functions.
(sieve-manage-make-process-buffer): Switch process buffer to unibyte.
(sieve-manage-open-server): Add `:coding 'raw-text-unix` to
`open-network-stream' call. Use unix EOLs in order to keep matching
CRLF (aka "\r\n") intact.
(sieve-manage-send): Make sure that UTF-8 multibyte characters are
properly encoded before sending data to the server.
(sieve-manage-getscript):
(sieve-manage-putscript): Use the changes above to fix down/uploading
scripts containing UTF-8 multibyte characters.
(sieve-manage-listscripts):
(sieve-manage-havespace)
(sieve-manage-getscript)
(sieve-manage-putscript):
(sieve-manage-deletescript):
(sieve-manage-setactive): Use the changes above to fix handling of
script names which contain UTF-8 multibyte characters.
(sieve-manage-parse-string):
(sieve-manage-getscript): Add handling of server responses with type
NO. Abort `sieve-manage-getscript' and show error message in message
area.
(sieve-manage-erase):
(sieve-manage-drop-next-answer):
(sieve-manage-parse-crlf): Return erased/dropped data (instead of nil).
(sieve-sasl-auth):
(sieve-manage-getscript):
(sieve-manage-erase):
(sieve-manage-open-server):
(sieve-manage-open):
(sieve-manage-send): Improve logging.
| -rw-r--r-- | lisp/net/sieve-manage.el | 125 |
1 files changed, 86 insertions, 39 deletions
diff --git a/lisp/net/sieve-manage.el b/lisp/net/sieve-manage.el index a39e35a53a1..381e1fcd4f8 100644 --- a/lisp/net/sieve-manage.el +++ b/lisp/net/sieve-manage.el | |||
| @@ -167,7 +167,52 @@ Valid states are `closed', `initial', `nonauth', and `auth'.") | |||
| 167 | (defvar sieve-manage-capability nil) | 167 | (defvar sieve-manage-capability nil) |
| 168 | 168 | ||
| 169 | ;; Internal utility functions | 169 | ;; Internal utility functions |
| 170 | (autoload 'mm-enable-multibyte "mm-util") | 170 | (defun sieve-manage--append-to-log (&rest args) |
| 171 | "Append ARGS to sieve-manage log buffer. | ||
| 172 | |||
| 173 | ARGS can be a string or a list of strings. | ||
| 174 | The buffer to use for logging is specifified via | ||
| 175 | `sieve-manage-log'. If it is nil, logging is disabled." | ||
| 176 | (when sieve-manage-log | ||
| 177 | (with-current-buffer (or (get-buffer sieve-manage-log) | ||
| 178 | (with-current-buffer | ||
| 179 | (get-buffer-create sieve-manage-log) | ||
| 180 | (set-buffer-multibyte nil) | ||
| 181 | (buffer-disable-undo))) | ||
| 182 | (goto-char (point-max)) | ||
| 183 | (apply #'insert args)))) | ||
| 184 | |||
| 185 | (defun sieve-manage--message (format-string &rest args) | ||
| 186 | "Wrapper around `message' which also logs to sieve manage log. | ||
| 187 | |||
| 188 | See `sieve-manage--append-to-log'." | ||
| 189 | (let ((ret (apply #'message | ||
| 190 | (concat "sieve-manage: " format-string) | ||
| 191 | args))) | ||
| 192 | (sieve-manage--append-to-log ret "\n") | ||
| 193 | ret)) | ||
| 194 | |||
| 195 | (defun sieve-manage--error (format-string &rest args) | ||
| 196 | "Wrapper around `error' which also logs to sieve manage log. | ||
| 197 | |||
| 198 | See `sieve-manage--append-to-log'." | ||
| 199 | (let ((msg (apply #'format | ||
| 200 | (concat "sieve-manage/ERROR: " format-string) | ||
| 201 | args))) | ||
| 202 | (sieve-manage--append-to-log msg "\n") | ||
| 203 | (error msg))) | ||
| 204 | |||
| 205 | (defun sieve-manage-encode (utf8-string) | ||
| 206 | "Convert UTF8-STRING to managesieve protocol octets." | ||
| 207 | (encode-coding-string utf8-string 'raw-text t)) | ||
| 208 | |||
| 209 | (defun sieve-manage-decode (octets &optional buffer) | ||
| 210 | "Convert managesieve protocol OCTETS to utf-8 string. | ||
| 211 | |||
| 212 | If optional BUFFER is non-nil, insert decoded string into BUFFER." | ||
| 213 | (when octets | ||
| 214 | ;; eol type unix is required to preserve "\r\n" | ||
| 215 | (decode-coding-string octets 'utf-8-unix t buffer))) | ||
| 171 | 216 | ||
| 172 | (defun sieve-manage-make-process-buffer () | 217 | (defun sieve-manage-make-process-buffer () |
| 173 | (with-current-buffer | 218 | (with-current-buffer |
| @@ -175,22 +220,19 @@ Valid states are `closed', `initial', `nonauth', and `auth'.") | |||
| 175 | sieve-manage-server | 220 | sieve-manage-server |
| 176 | sieve-manage-port)) | 221 | sieve-manage-port)) |
| 177 | (mapc #'make-local-variable sieve-manage-local-variables) | 222 | (mapc #'make-local-variable sieve-manage-local-variables) |
| 178 | (mm-enable-multibyte) | 223 | (set-buffer-multibyte nil) |
| 224 | (setq-local after-change-functions nil) | ||
| 179 | (buffer-disable-undo) | 225 | (buffer-disable-undo) |
| 180 | (current-buffer))) | 226 | (current-buffer))) |
| 181 | 227 | ||
| 182 | (defun sieve-manage-erase (&optional p buffer) | 228 | (defun sieve-manage-erase (&optional p buffer) |
| 183 | (let ((buffer (or buffer (current-buffer)))) | 229 | (with-current-buffer (or buffer (current-buffer)) |
| 184 | (and sieve-manage-log | 230 | (let* ((start (point-min)) |
| 185 | (with-current-buffer (get-buffer-create sieve-manage-log) | 231 | (end (or p (point-max))) |
| 186 | (mm-enable-multibyte) | 232 | (logdata (buffer-substring-no-properties start end))) |
| 187 | (buffer-disable-undo) | 233 | (sieve-manage--append-to-log logdata) |
| 188 | (goto-char (point-max)) | 234 | (delete-region start end) |
| 189 | (insert-buffer-substring buffer (with-current-buffer buffer | 235 | logdata))) |
| 190 | (point-min)) | ||
| 191 | (or p (with-current-buffer buffer | ||
| 192 | (point-max))))))) | ||
| 193 | (delete-region (point-min) (or p (point-max)))) | ||
| 194 | 236 | ||
| 195 | (defun sieve-manage-open-server (server port &optional stream buffer) | 237 | (defun sieve-manage-open-server (server port &optional stream buffer) |
| 196 | "Open network connection to SERVER on PORT. | 238 | "Open network connection to SERVER on PORT. |
| @@ -202,6 +244,8 @@ Return the buffer associated with the connection." | |||
| 202 | (open-network-stream | 244 | (open-network-stream |
| 203 | "SIEVE" buffer server port | 245 | "SIEVE" buffer server port |
| 204 | :type stream | 246 | :type stream |
| 247 | ;; eol type unix is required to preserve "\r\n" | ||
| 248 | :coding 'raw-text-unix | ||
| 205 | :capability-command "CAPABILITY\r\n" | 249 | :capability-command "CAPABILITY\r\n" |
| 206 | :end-of-command "^\\(OK\\|NO\\).*\n" | 250 | :end-of-command "^\\(OK\\|NO\\).*\n" |
| 207 | :success "^OK.*\n" | 251 | :success "^OK.*\n" |
| @@ -224,7 +268,7 @@ Return the buffer associated with the connection." | |||
| 224 | ;; Authenticators | 268 | ;; Authenticators |
| 225 | (defun sieve-sasl-auth (buffer mech) | 269 | (defun sieve-sasl-auth (buffer mech) |
| 226 | "Login to server using the SASL MECH method." | 270 | "Login to server using the SASL MECH method." |
| 227 | (message "sieve: Authenticating using %s..." mech) | 271 | (sieve-manage--message "Authenticating using %s..." mech) |
| 228 | (with-current-buffer buffer | 272 | (with-current-buffer buffer |
| 229 | (let* ((auth-info (auth-source-search :host sieve-manage-server | 273 | (let* ((auth-info (auth-source-search :host sieve-manage-server |
| 230 | :port "sieve" | 274 | :port "sieve" |
| @@ -275,11 +319,15 @@ Return the buffer associated with the connection." | |||
| 275 | (if (and (setq step (sasl-next-step client step)) | 319 | (if (and (setq step (sasl-next-step client step)) |
| 276 | (setq data (sasl-step-data step))) | 320 | (setq data (sasl-step-data step))) |
| 277 | ;; We got data for server but it's finished | 321 | ;; We got data for server but it's finished |
| 278 | (error "Server not ready for SASL data: %s" data) | 322 | (sieve-manage--error |
| 323 | "Server not ready for SASL data: %s" data) | ||
| 279 | ;; The authentication process is finished. | 324 | ;; The authentication process is finished. |
| 325 | (sieve-manage--message "Logged in as %s using %s" | ||
| 326 | user-name mech) | ||
| 280 | (throw 'done t))) | 327 | (throw 'done t))) |
| 281 | (unless (stringp rsp) | 328 | (unless (stringp rsp) |
| 282 | (error "Server aborted SASL authentication: %s" (caddr rsp))) | 329 | (sieve-manage--error |
| 330 | "Server aborted SASL authentication: %s" (caddr rsp))) | ||
| 283 | (sasl-step-set-data step (base64-decode-string rsp)) | 331 | (sasl-step-set-data step (base64-decode-string rsp)) |
| 284 | (setq step (sasl-next-step client step)) | 332 | (setq step (sasl-next-step client step)) |
| 285 | (sieve-manage-send | 333 | (sieve-manage-send |
| @@ -288,8 +336,7 @@ Return the buffer associated with the connection." | |||
| 288 | (base64-encode-string (sasl-step-data step) | 336 | (base64-encode-string (sasl-step-data step) |
| 289 | 'no-line-break) | 337 | 'no-line-break) |
| 290 | "\"") | 338 | "\"") |
| 291 | "")))) | 339 | ""))))))) |
| 292 | (message "sieve: Login using %s...done" mech)))) | ||
| 293 | 340 | ||
| 294 | (defun sieve-manage-cram-md5-p (buffer) | 341 | (defun sieve-manage-cram-md5-p (buffer) |
| 295 | (sieve-manage-capability "SASL" "CRAM-MD5" buffer)) | 342 | (sieve-manage-capability "SASL" "CRAM-MD5" buffer)) |
| @@ -353,7 +400,7 @@ to work in." | |||
| 353 | sieve-manage-default-stream) | 400 | sieve-manage-default-stream) |
| 354 | sieve-manage-auth (or auth | 401 | sieve-manage-auth (or auth |
| 355 | sieve-manage-auth)) | 402 | sieve-manage-auth)) |
| 356 | (message "sieve: Connecting to %s..." sieve-manage-server) | 403 | (sieve-manage--message "Connecting to %s..." sieve-manage-server) |
| 357 | (sieve-manage-open-server sieve-manage-server | 404 | (sieve-manage-open-server sieve-manage-server |
| 358 | sieve-manage-port | 405 | sieve-manage-port |
| 359 | sieve-manage-stream | 406 | sieve-manage-stream |
| @@ -368,7 +415,8 @@ to work in." | |||
| 368 | (setq sieve-manage-auth auth) | 415 | (setq sieve-manage-auth auth) |
| 369 | (cl-return))) | 416 | (cl-return))) |
| 370 | (unless sieve-manage-auth | 417 | (unless sieve-manage-auth |
| 371 | (error "Couldn't figure out authenticator for server"))) | 418 | (sieve-manage--error |
| 419 | "Couldn't figure out authenticator for server"))) | ||
| 372 | (sieve-manage-erase) | 420 | (sieve-manage-erase) |
| 373 | (current-buffer)))) | 421 | (current-buffer)))) |
| 374 | 422 | ||
| @@ -433,11 +481,7 @@ If NAME is nil, return the full server list of capabilities." | |||
| 433 | (defun sieve-manage-putscript (name content &optional buffer) | 481 | (defun sieve-manage-putscript (name content &optional buffer) |
| 434 | (with-current-buffer (or buffer (current-buffer)) | 482 | (with-current-buffer (or buffer (current-buffer)) |
| 435 | (sieve-manage-send (format "PUTSCRIPT \"%s\" {%d+}%s%s" name | 483 | (sieve-manage-send (format "PUTSCRIPT \"%s\" {%d+}%s%s" name |
| 436 | ;; Here we assume that the coding-system will | 484 | (length (sieve-manage-encode content)) |
| 437 | ;; replace each char with a single byte. | ||
| 438 | ;; This is always the case if `content' is | ||
| 439 | ;; a unibyte string. | ||
| 440 | (length content) | ||
| 441 | sieve-manage-client-eol content)) | 485 | sieve-manage-client-eol content)) |
| 442 | (sieve-manage-parse-okno))) | 486 | (sieve-manage-parse-okno))) |
| 443 | 487 | ||
| @@ -449,11 +493,10 @@ If NAME is nil, return the full server list of capabilities." | |||
| 449 | (defun sieve-manage-getscript (name output-buffer &optional buffer) | 493 | (defun sieve-manage-getscript (name output-buffer &optional buffer) |
| 450 | (with-current-buffer (or buffer (current-buffer)) | 494 | (with-current-buffer (or buffer (current-buffer)) |
| 451 | (sieve-manage-send (format "GETSCRIPT \"%s\"" name)) | 495 | (sieve-manage-send (format "GETSCRIPT \"%s\"" name)) |
| 452 | (let ((script (sieve-manage-parse-string))) | 496 | (sieve-manage-decode (sieve-manage-parse-string) |
| 453 | (sieve-manage-parse-crlf) | 497 | output-buffer) |
| 454 | (with-current-buffer output-buffer | 498 | (sieve-manage-parse-crlf) |
| 455 | (insert script)) | 499 | (sieve-manage-parse-okno))) |
| 456 | (sieve-manage-parse-okno)))) | ||
| 457 | 500 | ||
| 458 | (defun sieve-manage-setactive (name &optional buffer) | 501 | (defun sieve-manage-setactive (name &optional buffer) |
| 459 | (with-current-buffer (or buffer (current-buffer)) | 502 | (with-current-buffer (or buffer (current-buffer)) |
| @@ -478,6 +521,9 @@ If NAME is nil, return the full server list of capabilities." | |||
| 478 | (defun sieve-manage-ok-p (rsp) | 521 | (defun sieve-manage-ok-p (rsp) |
| 479 | (string= (downcase (or (car-safe rsp) "")) "ok")) | 522 | (string= (downcase (or (car-safe rsp) "")) "ok")) |
| 480 | 523 | ||
| 524 | (defun sieve-manage-no-p (rsp) | ||
| 525 | (string= (downcase (or (car-safe rsp) "")) "no")) | ||
| 526 | |||
| 481 | (defun sieve-manage-is-okno () | 527 | (defun sieve-manage-is-okno () |
| 482 | (when (looking-at (concat | 528 | (when (looking-at (concat |
| 483 | "^\\(OK\\|NO\\)\\( (\\([^)]+\\))\\)?\\( \\(.*\\)\\)?" | 529 | "^\\(OK\\|NO\\)\\( (\\([^)]+\\))\\)?\\( \\(.*\\)\\)?" |
| @@ -528,7 +574,11 @@ to local variable `sieve-manage-capability'." | |||
| 528 | (while (null rsp) | 574 | (while (null rsp) |
| 529 | (accept-process-output (get-buffer-process (current-buffer)) 1) | 575 | (accept-process-output (get-buffer-process (current-buffer)) 1) |
| 530 | (goto-char (point-min)) | 576 | (goto-char (point-min)) |
| 531 | (setq rsp (sieve-manage-is-string))) | 577 | (unless (setq rsp (sieve-manage-is-string)) |
| 578 | (when (sieve-manage-no-p (sieve-manage-is-okno)) | ||
| 579 | ;; simple `error' is enough since `sieve-manage-erase' | ||
| 580 | ;; already adds the server response to the log | ||
| 581 | (error (sieve-manage-erase))))) | ||
| 532 | (sieve-manage-erase (point)) | 582 | (sieve-manage-erase (point)) |
| 533 | rsp)) | 583 | rsp)) |
| 534 | 584 | ||
| @@ -540,7 +590,8 @@ to local variable `sieve-manage-capability'." | |||
| 540 | (let (tmp rsp data) | 590 | (let (tmp rsp data) |
| 541 | (while (null rsp) | 591 | (while (null rsp) |
| 542 | (while (null (or (setq rsp (sieve-manage-is-okno)) | 592 | (while (null (or (setq rsp (sieve-manage-is-okno)) |
| 543 | (setq tmp (sieve-manage-is-string)))) | 593 | (setq tmp (sieve-manage-decode |
| 594 | (sieve-manage-is-string))))) | ||
| 544 | (accept-process-output (get-buffer-process (current-buffer)) 1) | 595 | (accept-process-output (get-buffer-process (current-buffer)) 1) |
| 545 | (goto-char (point-min))) | 596 | (goto-char (point-min))) |
| 546 | (when tmp | 597 | (when tmp |
| @@ -559,13 +610,9 @@ to local variable `sieve-manage-capability'." | |||
| 559 | rsp))) | 610 | rsp))) |
| 560 | 611 | ||
| 561 | (defun sieve-manage-send (cmdstr) | 612 | (defun sieve-manage-send (cmdstr) |
| 562 | (setq cmdstr (concat cmdstr sieve-manage-client-eol)) | 613 | (setq cmdstr (sieve-manage-encode |
| 563 | (and sieve-manage-log | 614 | (concat cmdstr sieve-manage-client-eol))) |
| 564 | (with-current-buffer (get-buffer-create sieve-manage-log) | 615 | (sieve-manage--append-to-log cmdstr) |
| 565 | (mm-enable-multibyte) | ||
| 566 | (buffer-disable-undo) | ||
| 567 | (goto-char (point-max)) | ||
| 568 | (insert cmdstr))) | ||
| 569 | (process-send-string sieve-manage-process cmdstr)) | 616 | (process-send-string sieve-manage-process cmdstr)) |
| 570 | 617 | ||
| 571 | (provide 'sieve-manage) | 618 | (provide 'sieve-manage) |