aboutsummaryrefslogtreecommitdiffstats
path: root/lisp/net
diff options
context:
space:
mode:
authorLars Ingebrigtsen2016-02-24 13:04:03 +1100
committerLars Ingebrigtsen2016-02-24 13:04:03 +1100
commit21fe2ebec8b63d5fd0a570ed0c907802ab83f991 (patch)
treef7fe7b6b4b2a21667cb66a1fdf7d470c7ec292a0 /lisp/net
parente1d749bd7e0d68ab063eae3927caede6039a33cf (diff)
downloademacs-21fe2ebec8b63d5fd0a570ed0c907802ab83f991.tar.gz
emacs-21fe2ebec8b63d5fd0a570ed0c907802ab83f991.zip
Move low-level library files from the lisp/gnus directory
The files moved from lisp/gnus are: auth-source.el -> / compface.el -> /image ecomplete.el -> / flow-fill.el -> /mail gravatar.el -> /image gssapi.el -> /net html2text.el -> /net ietf-drums.el -> /mail mail-parse.el -> /mail mail-prsvr.el -> /mail mailcap.el -> /net plstore.el -> / pop3.el -> /net qp.el -> /mail registry.el -> / rfc1843.el -> /international rfc2045.el -> /mail rfc2047.el -> /mail rfc2231.el -> /mail rtree.el -> / sieve-manage.el -> /net sieve-mode.el -> /net sieve.el -> /net starttls.el -> /net utf7.el -> /international yenc.el -> /mail
Diffstat (limited to 'lisp/net')
-rw-r--r--lisp/net/html2text.el461
-rw-r--r--lisp/net/mailcap.el1054
-rw-r--r--lisp/net/pop3.el914
-rw-r--r--lisp/net/sieve-manage.el575
-rw-r--r--lisp/net/sieve-mode.el221
-rw-r--r--lisp/net/sieve.el372
-rw-r--r--lisp/net/starttls.el304
7 files changed, 3901 insertions, 0 deletions
diff --git a/lisp/net/html2text.el b/lisp/net/html2text.el
new file mode 100644
index 00000000000..2b1c2057bb4
--- /dev/null
+++ b/lisp/net/html2text.el
@@ -0,0 +1,461 @@
1;;; html2text.el --- a simple html to plain text converter -*- coding: utf-8 -*-
2
3;; Copyright (C) 2002-2016 Free Software Foundation, Inc.
4
5;; Author: Joakim Hove <hove@phys.ntnu.no>
6
7;; This file is part of GNU Emacs.
8
9;; GNU Emacs is free software: you can redistribute it and/or modify
10;; it under the terms of the GNU General Public License as published by
11;; the Free Software Foundation, either version 3 of the License, or
12;; (at your option) any later version.
13
14;; GNU Emacs is distributed in the hope that it will be useful,
15;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17;; GNU General Public License for more details.
18
19;; You should have received a copy of the GNU General Public License
20;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
21
22;;; Commentary:
23
24;; These functions provide a simple way to wash/clean html infected
25;; mails. Definitely do not work in all cases, but some improvement
26;; in readability is generally obtained. Formatting is only done in
27;; the buffer, so the next time you enter the article it will be
28;; "re-htmlized".
29;;
30;; The main function is `html2text'.
31
32;;; Code:
33
34;;
35;; <Global variables>
36;;
37
38(eval-when-compile
39 (require 'cl))
40
41(defvar html2text-format-single-element-list '(("hr" . html2text-clean-hr)))
42
43(defvar html2text-replace-list
44 '(("&acute;" . "`")
45 ("&amp;" . "&")
46 ("&apos;" . "'")
47 ("&brvbar;" . "|")
48 ("&cent;" . "c")
49 ("&circ;" . "^")
50 ("&copy;" . "(C)")
51 ("&curren;" . "(#)")
52 ("&deg;" . "degree")
53 ("&divide;" . "/")
54 ("&euro;" . "e")
55 ("&frac12;" . "1/2")
56 ("&gt;" . ">")
57 ("&iquest;" . "?")
58 ("&laquo;" . "<<")
59 ("&ldquo" . "\"")
60 ("&lsaquo;" . "(")
61 ("&lsquo;" . "`")
62 ("&lt;" . "<")
63 ("&mdash;" . "--")
64 ("&nbsp;" . " ")
65 ("&ndash;" . "-")
66 ("&permil;" . "%%")
67 ("&plusmn;" . "+-")
68 ("&pound;" . "£")
69 ("&quot;" . "\"")
70 ("&raquo;" . ">>")
71 ("&rdquo" . "\"")
72 ("&reg;" . "(R)")
73 ("&rsaquo;" . ")")
74 ("&rsquo;" . "'")
75 ("&sect;" . "§")
76 ("&sup1;" . "^1")
77 ("&sup2;" . "^2")
78 ("&sup3;" . "^3")
79 ("&tilde;" . "~"))
80 "The map of entity to text.
81
82This is an alist were each element is a dotted pair consisting of an
83old string, and a replacement string. This replacement is done by the
84function `html2text-substitute' which basically performs a
85`replace-string' operation for every element in the list. This is
86completely verbatim - without any use of REGEXP.")
87
88(defvar html2text-remove-tag-list
89 '("html" "body" "p" "img" "dir" "head" "div" "br" "font" "title" "meta")
90 "A list of removable tags.
91
92This is a list of tags which should be removed, without any
93formatting. Note that tags in the list are presented *without*
94any \"<\" or \">\". All occurrences of a tag appearing in this
95list are removed, irrespective of whether it is a closing or
96opening tag, or if the tag has additional attributes. The
97deletion is done by the function `html2text-remove-tags'.
98
99For instance the text:
100
101\"Here comes something <font size\"+3\" face=\"Helvetica\"> big </font>.\"
102
103will be reduced to:
104
105\"Here comes something big.\"
106
107If this list contains the element \"font\".")
108
109(defvar html2text-format-tag-list
110 '(("b" . html2text-clean-bold)
111 ("strong" . html2text-clean-bold)
112 ("u" . html2text-clean-underline)
113 ("i" . html2text-clean-italic)
114 ("em" . html2text-clean-italic)
115 ("blockquote" . html2text-clean-blockquote)
116 ("a" . html2text-clean-anchor)
117 ("ul" . html2text-clean-ul)
118 ("ol" . html2text-clean-ol)
119 ("dl" . html2text-clean-dl)
120 ("center" . html2text-clean-center))
121 "An alist of tags and processing functions.
122
123This is an alist where each dotted pair consists of a tag, and then
124the name of a function to be called when this tag is found. The
125function is called with the arguments p1, p2, p3 and p4. These are
126demonstrated below:
127
128\"<b> This is bold text </b>\"
129 ^ ^ ^ ^
130 | | | |
131p1 p2 p3 p4
132
133Then the called function will typically format the text somewhat and
134remove the tags.")
135
136(defvar html2text-remove-tag-list2 '("li" "dt" "dd" "meta")
137 "Another list of removable tags.
138
139This is a list of tags which are removed similarly to the list
140`html2text-remove-tag-list' - but these tags are retained for the
141formatting, and then moved afterward.")
142
143;;
144;; </Global variables>
145;;
146
147;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
148;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
149
150;;
151;; <Utility functions>
152;;
153
154
155(defun html2text-replace-string (from-string to-string min max)
156 "Replace FROM-STRING with TO-STRING in region from MIN to MAX."
157 (goto-char min)
158 (let ((delta (- (string-width to-string) (string-width from-string)))
159 (change 0))
160 (while (search-forward from-string max t)
161 (replace-match to-string)
162 (setq change (+ change delta)))
163 change))
164
165;;
166;; </Utility functions>
167;;
168
169;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
170;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
171
172;;
173;; <Functions related to attributes> i.e. <font size=+3>
174;;
175
176(defun html2text-attr-value (list attribute)
177 "Get value of ATTRIBUTE from LIST."
178 (nth 1 (assoc attribute list)))
179
180(defun html2text-get-attr (p1 p2)
181 (goto-char p1)
182 (re-search-forward "\\s-+" p2 t)
183 (let (attr-list)
184 (while (re-search-forward "[-a-z0-9._]+" p2 t)
185 (setq attr-list
186 (cons
187 (list (match-string 0)
188 (when (looking-at "\\s-*=")
189 (goto-char (match-end 0))
190 (skip-chars-forward "[:space:]")
191 (when (or (looking-at "\"[^\"]*\"\\|'[^']*'")
192 (looking-at "[-a-z0-9._:]+"))
193 (goto-char (match-end 0))
194 (match-string 0))))
195 attr-list)))
196 attr-list))
197
198;;
199;; </Functions related to attributes>
200;;
201
202;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
203;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
204
205;;
206;; <Functions to be called to format a tag-pair>
207;;
208(defun html2text-clean-list-items (p1 p2 list-type)
209 (goto-char p1)
210 (let ((item-nr 0)
211 (items 0))
212 (while (search-forward "<li>" p2 t)
213 (setq items (1+ items)))
214 (goto-char p1)
215 (while (< item-nr items)
216 (setq item-nr (1+ item-nr))
217 (search-forward "<li>" (point-max) t)
218 (cond
219 ((string= list-type "ul") (insert " o "))
220 ((string= list-type "ol") (insert (format " %s: " item-nr)))
221 (t (insert " x "))))))
222
223(defun html2text-clean-dtdd (p1 p2)
224 (goto-char p1)
225 (let ((items 0)
226 (item-nr 0))
227 (while (search-forward "<dt>" p2 t)
228 (setq items (1+ items)))
229 (goto-char p1)
230 (while (< item-nr items)
231 (setq item-nr (1+ item-nr))
232 (re-search-forward "<dt>\\([ ]*\\)" (point-max) t)
233 (when (match-string 1)
234 (delete-region (point) (- (point) (string-width (match-string 1)))))
235 (let ((def-p1 (point))
236 (def-p2 0))
237 (re-search-forward "\\([ ]*\\)\\(</dt>\\|<dd>\\)" (point-max) t)
238 (if (match-string 1)
239 (progn
240 (let* ((mw1 (string-width (match-string 1)))
241 (mw2 (string-width (match-string 2)))
242 (mw (+ mw1 mw2)))
243 (goto-char (- (point) mw))
244 (delete-region (point) (+ (point) mw1))
245 (setq def-p2 (point))))
246 (setq def-p2 (- (point) (string-width (match-string 2)))))
247 (put-text-property def-p1 def-p2 'face 'bold)))))
248
249(defun html2text-delete-tags (p1 p2 p3 p4)
250 (delete-region p1 p2)
251 (delete-region (- p3 (- p2 p1)) (- p4 (- p2 p1))))
252
253(defun html2text-delete-single-tag (p1 p2)
254 (delete-region p1 p2))
255
256(defun html2text-clean-hr (p1 p2)
257 (html2text-delete-single-tag p1 p2)
258 (goto-char p1)
259 (newline 1)
260 (insert (make-string fill-column ?-)))
261
262(defun html2text-clean-ul (p1 p2 p3 p4)
263 (html2text-delete-tags p1 p2 p3 p4)
264 (html2text-clean-list-items p1 (- p3 (- p1 p2)) "ul"))
265
266(defun html2text-clean-ol (p1 p2 p3 p4)
267 (html2text-delete-tags p1 p2 p3 p4)
268 (html2text-clean-list-items p1 (- p3 (- p1 p2)) "ol"))
269
270(defun html2text-clean-dl (p1 p2 p3 p4)
271 (html2text-delete-tags p1 p2 p3 p4)
272 (html2text-clean-dtdd p1 (- p3 (- p1 p2))))
273
274(defun html2text-clean-center (p1 p2 p3 p4)
275 (html2text-delete-tags p1 p2 p3 p4)
276 (center-region p1 (- p3 (- p2 p1))))
277
278(defun html2text-clean-bold (p1 p2 p3 p4)
279 (put-text-property p2 p3 'face 'bold)
280 (html2text-delete-tags p1 p2 p3 p4))
281
282(defun html2text-clean-title (p1 p2 p3 p4)
283 (put-text-property p2 p3 'face 'bold)
284 (html2text-delete-tags p1 p2 p3 p4))
285
286(defun html2text-clean-underline (p1 p2 p3 p4)
287 (put-text-property p2 p3 'face 'underline)
288 (html2text-delete-tags p1 p2 p3 p4))
289
290(defun html2text-clean-italic (p1 p2 p3 p4)
291 (put-text-property p2 p3 'face 'italic)
292 (html2text-delete-tags p1 p2 p3 p4))
293
294(defun html2text-clean-font (p1 p2 p3 p4)
295 (html2text-delete-tags p1 p2 p3 p4))
296
297(defun html2text-clean-blockquote (p1 p2 p3 p4)
298 (html2text-delete-tags p1 p2 p3 p4))
299
300(defun html2text-clean-anchor (p1 p2 p3 p4)
301 ;; If someone can explain how to make the URL clickable I will surely
302 ;; improve upon this.
303 ;; Maybe `goto-addr.el' can be used here.
304 (let* ((attr-list (html2text-get-attr p1 p2))
305 (href (html2text-attr-value attr-list "href")))
306 (delete-region p1 p4)
307 (when href
308 (goto-char p1)
309 (insert (if (string-match "\\`['\"].*['\"]\\'" href)
310 (substring href 1 -1) href))
311 (put-text-property p1 (point) 'face 'bold))))
312
313;;
314;; </Functions to be called to format a tag-pair>
315;;
316
317;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
318;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
319
320;;
321;; <Functions to be called to fix up paragraphs>
322;;
323
324(defun html2text-fix-paragraph (p1 p2)
325 (goto-char p1)
326 (let ((refill-start)
327 (refill-stop))
328 (when (re-search-forward "<br>$" p2 t)
329 (goto-char p1)
330 (when (re-search-forward ".+[^<][^b][^r][^>]$" p2 t)
331 (beginning-of-line)
332 (setq refill-start (point))
333 (goto-char p2)
334 (re-search-backward ".+[^<][^b][^r][^>]$" refill-start t)
335 (forward-line 1)
336 (end-of-line)
337 ;; refill-stop should ideally be adjusted to
338 ;; accommodate the "<br>" strings which are removed
339 ;; between refill-start and refill-stop. Can simply
340 ;; be returned from my-replace-string
341 (setq refill-stop (+ (point)
342 (html2text-replace-string
343 "<br>" ""
344 refill-start (point))))
345 ;; (message "Point = %s refill-stop = %s" (point) refill-stop)
346 ;; (sleep-for 4)
347 (fill-region refill-start refill-stop))))
348 (html2text-replace-string "<br>" "" p1 p2))
349
350;;
351;; This one is interactive ...
352;;
353(defun html2text-fix-paragraphs ()
354 "This _tries_ to fix up the paragraphs - this is done in quite a ad-hook
355fashion, quite close to pure guess-work. It does work in some cases though."
356 (interactive)
357 (goto-char (point-min))
358 (while (re-search-forward "^<br>$" nil t)
359 (delete-region (match-beginning 0) (match-end 0)))
360 ;; Removing lonely <br> on a single line, if they are left intact we
361 ;; don't have any paragraphs at all.
362 (goto-char (point-min))
363 (while (not (eobp))
364 (let ((p1 (point)))
365 (forward-paragraph 1)
366 ;;(message "Kaller fix med p1=%s p2=%s " p1 (1- (point))) (sleep-for 5)
367 (html2text-fix-paragraph p1 (1- (point)))
368 (goto-char p1)
369 (when (not (eobp))
370 (forward-paragraph 1)))))
371
372;;
373;; </Functions to be called to fix up paragraphs>
374;;
375
376;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
377;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
378
379;;
380;; <Interactive functions>
381;;
382
383(defun html2text-remove-tags (tag-list)
384 "Removes the tags listed in the list `html2text-remove-tag-list'.
385See the documentation for that variable."
386 (interactive)
387 (dolist (tag tag-list)
388 (goto-char (point-min))
389 (while (re-search-forward (format "\\(</?%s[^>]*>\\)" tag) (point-max) t)
390 (delete-region (match-beginning 0) (match-end 0)))))
391
392(defun html2text-format-tags ()
393 "See the variable `html2text-format-tag-list' for documentation."
394 (interactive)
395 (dolist (tag-and-function html2text-format-tag-list)
396 (let ((tag (car tag-and-function))
397 (function (cdr tag-and-function)))
398 (goto-char (point-min))
399 (while (re-search-forward (format "\\(<%s\\( [^>]*\\)?>\\)" tag)
400 (point-max) t)
401 (let ((p1)
402 (p2 (point))
403 (p3) (p4))
404 (search-backward "<" (point-min) t)
405 (setq p1 (point))
406 (unless (search-forward (format "</%s>" tag) (point-max) t)
407 (goto-char p2)
408 (insert (format "</%s>" tag)))
409 (setq p4 (point))
410 (search-backward "</" (point-min) t)
411 (setq p3 (point))
412 (funcall function p1 p2 p3 p4)
413 (goto-char p1))))))
414
415(defun html2text-substitute ()
416 "See the variable `html2text-replace-list' for documentation."
417 (interactive)
418 (dolist (e html2text-replace-list)
419 (goto-char (point-min))
420 (let ((old-string (car e))
421 (new-string (cdr e)))
422 (html2text-replace-string old-string new-string (point-min) (point-max)))))
423
424(defun html2text-format-single-elements ()
425 (interactive)
426 (dolist (tag-and-function html2text-format-single-element-list)
427 (let ((tag (car tag-and-function))
428 (function (cdr tag-and-function)))
429 (goto-char (point-min))
430 (while (re-search-forward (format "\\(<%s\\( [^>]*\\)?>\\)" tag)
431 (point-max) t)
432 (let ((p1)
433 (p2 (point)))
434 (search-backward "<" (point-min) t)
435 (setq p1 (point))
436 (funcall function p1 p2))))))
437
438;;
439;; Main function
440;;
441
442;;;###autoload
443(defun html2text ()
444 "Convert HTML to plain text in the current buffer."
445 (interactive)
446 (save-excursion
447 (let ((case-fold-search t)
448 (buffer-read-only))
449 (html2text-remove-tags html2text-remove-tag-list)
450 (html2text-format-tags)
451 (html2text-remove-tags html2text-remove-tag-list2)
452 (html2text-substitute)
453 (html2text-format-single-elements)
454 (html2text-fix-paragraphs))))
455
456;;
457;; </Interactive functions>
458;;
459(provide 'html2text)
460
461;;; html2text.el ends here
diff --git a/lisp/net/mailcap.el b/lisp/net/mailcap.el
new file mode 100644
index 00000000000..609a8f4d64b
--- /dev/null
+++ b/lisp/net/mailcap.el
@@ -0,0 +1,1054 @@
1;;; mailcap.el --- MIME media types configuration
2
3;; Copyright (C) 1998-2016 Free Software Foundation, Inc.
4
5;; Author: William M. Perry <wmperry@aventail.com>
6;; Lars Magne Ingebrigtsen <larsi@gnus.org>
7;; Keywords: news, mail, multimedia
8
9;; This file is part of GNU Emacs.
10
11;; GNU Emacs is free software: you can redistribute it and/or modify
12;; it under the terms of the GNU General Public License as published by
13;; the Free Software Foundation, either version 3 of the License, or
14;; (at your option) any later version.
15
16;; GNU Emacs is distributed in the hope that it will be useful,
17;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19;; GNU General Public License for more details.
20
21;; You should have received a copy of the GNU General Public License
22;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
23
24;;; Commentary:
25
26;; Provides configuration of MIME media types from directly from Lisp
27;; and via the usual mailcap mechanism (RFC 1524). Deals with
28;; mime.types similarly.
29
30;;; Code:
31
32(eval-when-compile (require 'cl))
33(autoload 'mail-header-parse-content-type "mail-parse")
34
35(defgroup mailcap nil
36 "Definition of viewers for MIME types."
37 :version "21.1"
38 :group 'mime)
39
40(defvar mailcap-parse-args-syntax-table
41 (let ((table (copy-syntax-table emacs-lisp-mode-syntax-table)))
42 (modify-syntax-entry ?' "\"" table)
43 (modify-syntax-entry ?` "\"" table)
44 (modify-syntax-entry ?{ "(" table)
45 (modify-syntax-entry ?} ")" table)
46 table)
47 "A syntax table for parsing SGML attributes.")
48
49(defvar mailcap-print-command
50 (mapconcat 'identity
51 (cons (if (boundp 'lpr-command)
52 lpr-command
53 "lpr")
54 (when (boundp 'lpr-switches)
55 (if (stringp lpr-switches)
56 (list lpr-switches)
57 lpr-switches)))
58 " ")
59 "Shell command (including switches) used to print PostScript files.")
60
61;; Postpone using defcustom for this as it's so big and we essentially
62;; have to have two copies of the data around then. Perhaps just
63;; customize the Lisp viewers and rely on the normal configuration
64;; files for the rest? -- fx
65(defvar mailcap-mime-data
66 `(("application"
67 ("vnd\\.ms-excel"
68 (viewer . "gnumeric %s")
69 (test . (getenv "DISPLAY"))
70 (type . "application/vnd.ms-excel"))
71 ("x-x509-ca-cert"
72 (viewer . ssl-view-site-cert)
73 (type . "application/x-x509-ca-cert"))
74 ("x-x509-user-cert"
75 (viewer . ssl-view-user-cert)
76 (type . "application/x-x509-user-cert"))
77 ("octet-stream"
78 (viewer . mailcap-save-binary-file)
79 (non-viewer . t)
80 (type . "application/octet-stream"))
81 ("dvi"
82 (viewer . "xdvi -safer %s")
83 (test . (eq window-system 'x))
84 ("needsx11")
85 (type . "application/dvi")
86 ("print" . "dvips -qRP %s"))
87 ("dvi"
88 (viewer . "dvitty %s")
89 (test . (not (getenv "DISPLAY")))
90 (type . "application/dvi")
91 ("print" . "dvips -qRP %s"))
92 ("emacs-lisp"
93 (viewer . mailcap-maybe-eval)
94 (type . "application/emacs-lisp"))
95 ("x-emacs-lisp"
96 (viewer . mailcap-maybe-eval)
97 (type . "application/x-emacs-lisp"))
98 ("x-tar"
99 (viewer . mailcap-save-binary-file)
100 (non-viewer . t)
101 (type . "application/x-tar"))
102 ("x-latex"
103 (viewer . tex-mode)
104 (type . "application/x-latex"))
105 ("x-tex"
106 (viewer . tex-mode)
107 (type . "application/x-tex"))
108 ("latex"
109 (viewer . tex-mode)
110 (type . "application/latex"))
111 ("tex"
112 (viewer . tex-mode)
113 (type . "application/tex"))
114 ("texinfo"
115 (viewer . texinfo-mode)
116 (type . "application/tex"))
117 ("zip"
118 (viewer . mailcap-save-binary-file)
119 (non-viewer . t)
120 (type . "application/zip")
121 ("copiousoutput"))
122 ("pdf"
123 (viewer . pdf-view-mode)
124 (type . "application/pdf")
125 (test . (eq window-system 'x)))
126 ("pdf"
127 (viewer . doc-view-mode)
128 (type . "application/pdf")
129 (test . (eq window-system 'x)))
130 ("pdf"
131 (viewer . "gv -safer %s")
132 (type . "application/pdf")
133 (test . window-system)
134 ("print" . ,(concat "pdf2ps %s - | " mailcap-print-command)))
135 ("pdf"
136 (viewer . "gpdf %s")
137 (type . "application/pdf")
138 ("print" . ,(concat "pdftops %s - | " mailcap-print-command))
139 (test . (eq window-system 'x)))
140 ("pdf"
141 (viewer . "xpdf %s")
142 (type . "application/pdf")
143 ("print" . ,(concat "pdftops %s - | " mailcap-print-command))
144 (test . (eq window-system 'x)))
145 ("pdf"
146 (viewer . ,(concat "pdftotext %s -"))
147 (type . "application/pdf")
148 ("print" . ,(concat "pdftops %s - | " mailcap-print-command))
149 ("copiousoutput"))
150 ("postscript"
151 (viewer . "gv -safer %s")
152 (type . "application/postscript")
153 (test . window-system)
154 ("print" . ,(concat mailcap-print-command " %s"))
155 ("needsx11"))
156 ("postscript"
157 (viewer . "ghostview -dSAFER %s")
158 (type . "application/postscript")
159 (test . (eq window-system 'x))
160 ("print" . ,(concat mailcap-print-command " %s"))
161 ("needsx11"))
162 ("postscript"
163 (viewer . "ps2ascii %s")
164 (type . "application/postscript")
165 (test . (not (getenv "DISPLAY")))
166 ("print" . ,(concat mailcap-print-command " %s"))
167 ("copiousoutput"))
168 ("sieve"
169 (viewer . sieve-mode)
170 (type . "application/sieve"))
171 ("pgp-keys"
172 (viewer . "gpg --import --interactive --verbose")
173 (type . "application/pgp-keys")
174 ("needsterminal")))
175 ("audio"
176 ("x-mpeg"
177 (viewer . "maplay %s")
178 (type . "audio/x-mpeg"))
179 (".*"
180 (viewer . "showaudio")
181 (type . "audio/*")))
182 ("message"
183 ("rfc-*822"
184 (viewer . mm-view-message)
185 (test . (and (featurep 'gnus)
186 (gnus-alive-p)))
187 (type . "message/rfc822"))
188 ("rfc-*822"
189 (viewer . vm-mode)
190 (type . "message/rfc822"))
191 ("rfc-*822"
192 (viewer . view-mode)
193 (type . "message/rfc822")))
194 ("image"
195 ("x-xwd"
196 (viewer . "xwud -in %s")
197 (type . "image/x-xwd")
198 ("compose" . "xwd -frame > %s")
199 (test . (eq window-system 'x))
200 ("needsx11"))
201 ("x11-dump"
202 (viewer . "xwud -in %s")
203 (type . "image/x-xwd")
204 ("compose" . "xwd -frame > %s")
205 (test . (eq window-system 'x))
206 ("needsx11"))
207 ("windowdump"
208 (viewer . "xwud -in %s")
209 (type . "image/x-xwd")
210 ("compose" . "xwd -frame > %s")
211 (test . (eq window-system 'x))
212 ("needsx11"))
213 (".*"
214 (viewer . "display %s")
215 (type . "image/*")
216 (test . (eq window-system 'x))
217 ("needsx11"))
218 (".*"
219 (viewer . "ee %s")
220 (type . "image/*")
221 (test . (eq window-system 'x))
222 ("needsx11")))
223 ("text"
224 ("plain"
225 (viewer . view-mode)
226 (type . "text/plain"))
227 ("plain"
228 (viewer . fundamental-mode)
229 (type . "text/plain"))
230 ("enriched"
231 (viewer . enriched-decode)
232 (type . "text/enriched"))
233 ("dns"
234 (viewer . dns-mode)
235 (type . "text/dns")))
236 ("video"
237 ("mpeg"
238 (viewer . "mpeg_play %s")
239 (type . "video/mpeg")
240 (test . (eq window-system 'x))
241 ("needsx11")))
242 ("x-world"
243 ("x-vrml"
244 (viewer . "webspace -remote %s -URL %u")
245 (type . "x-world/x-vrml")
246 ("description"
247 "VRML document")))
248 ("archive"
249 ("tar"
250 (viewer . tar-mode)
251 (type . "archive/tar"))))
252 "The mailcap structure is an assoc list of assoc lists.
2531st assoc list is keyed on the major content-type
2542nd assoc list is keyed on the minor content-type (which can be a regexp)
255
256Which looks like:
257-----------------
258 ((\"application\"
259 (\"postscript\" . <info>))
260 (\"text\"
261 (\"plain\" . <info>)))
262
263Where <info> is another assoc list of the various information
264related to the mailcap RFC 1524. This is keyed on the lowercase
265attribute name (viewer, test, etc). This looks like:
266 ((viewer . VIEWERINFO)
267 (test . TESTINFO)
268 (xxxx . \"STRING\")
269 FLAG)
270
271Where VIEWERINFO specifies how the content-type is viewed. Can be
272a string, in which case it is run through a shell, with appropriate
273parameters, or a symbol, in which case the symbol is `funcall'ed if
274and only if it exists as a function, with the buffer as an argument.
275
276TESTINFO is a test for the viewer's applicability, or nil. If nil, it
277means the viewer is always valid. If it is a Lisp function, it is
278called with a list of items from any extra fields from the
279Content-Type header as argument to return a boolean value for the
280validity. Otherwise, if it is a non-function Lisp symbol or list
281whose car is a symbol, it is `eval'led to yield the validity. If it
282is a string or list of strings, it represents a shell command to run
283to return a true or false shell value for the validity.")
284(put 'mailcap-mime-data 'risky-local-variable t)
285
286(defcustom mailcap-download-directory nil
287 "*Directory to which `mailcap-save-binary-file' downloads files by default.
288nil means your home directory."
289 :type '(choice (const :tag "Home directory" nil)
290 directory)
291 :group 'mailcap)
292
293(defvar mailcap-poor-system-types
294 '(ms-dos windows-nt)
295 "Systems that don't have a Unix-like directory hierarchy.")
296
297;;;
298;;; Utility functions
299;;;
300
301(defun mailcap-save-binary-file ()
302 (goto-char (point-min))
303 (unwind-protect
304 (let ((file (read-file-name
305 "Filename to save as: "
306 (or mailcap-download-directory "~/")))
307 (require-final-newline nil))
308 (write-region (point-min) (point-max) file))
309 (kill-buffer (current-buffer))))
310
311(defvar mailcap-maybe-eval-warning
312 "*** WARNING ***
313
314This MIME part contains untrusted and possibly harmful content.
315If you evaluate the Emacs Lisp code contained in it, a lot of nasty
316things can happen. Please examine the code very carefully before you
317instruct Emacs to evaluate it. You can browse the buffer containing
318the code using \\[scroll-other-window].
319
320If you are unsure what to do, please answer \"no\"."
321 "Text of warning message displayed by `mailcap-maybe-eval'.
322Make sure that this text consists only of few text lines. Otherwise,
323Gnus might fail to display all of it.")
324
325(defun mailcap-maybe-eval ()
326 "Maybe evaluate a buffer of Emacs Lisp code."
327 (let ((lisp-buffer (current-buffer)))
328 (goto-char (point-min))
329 (when
330 (save-window-excursion
331 (delete-other-windows)
332 (let ((buffer (get-buffer-create (generate-new-buffer-name
333 "*Warning*"))))
334 (unwind-protect
335 (with-current-buffer buffer
336 (insert (substitute-command-keys
337 mailcap-maybe-eval-warning))
338 (goto-char (point-min))
339 (display-buffer buffer)
340 (yes-or-no-p "This is potentially dangerous emacs-lisp code, evaluate it? "))
341 (kill-buffer buffer))))
342 (eval-buffer (current-buffer)))
343 (when (buffer-live-p lisp-buffer)
344 (with-current-buffer lisp-buffer
345 (emacs-lisp-mode)))))
346
347
348;;;
349;;; The mailcap parser
350;;;
351
352(defun mailcap-replace-regexp (regexp to-string)
353 ;; Quiet replace-regexp.
354 (goto-char (point-min))
355 (while (re-search-forward regexp nil t)
356 (replace-match to-string t nil)))
357
358(defvar mailcap-parsed-p nil)
359
360(defun mailcap-parse-mailcaps (&optional path force)
361 "Parse out all the mailcaps specified in a path string PATH.
362Components of PATH are separated by the `path-separator' character
363appropriate for this system. If FORCE, re-parse even if already
364parsed. If PATH is omitted, use the value of environment variable
365MAILCAPS if set; otherwise (on Unix) use the path from RFC 1524, plus
366/usr/local/etc/mailcap."
367 (interactive (list nil t))
368 (when (or (not mailcap-parsed-p)
369 force)
370 (cond
371 (path nil)
372 ((getenv "MAILCAPS") (setq path (getenv "MAILCAPS")))
373 ((memq system-type mailcap-poor-system-types)
374 (setq path '("~/.mailcap" "~/mail.cap" "~/etc/mail.cap")))
375 (t (setq path
376 ;; This is per RFC 1524, specifically
377 ;; with /usr before /usr/local.
378 '("~/.mailcap" "/etc/mailcap" "/usr/etc/mailcap"
379 "/usr/local/etc/mailcap"))))
380 (let ((fnames (reverse
381 (if (stringp path)
382 (split-string path path-separator t)
383 path)))
384 fname)
385 (while fnames
386 (setq fname (car fnames))
387 (if (and (file-readable-p fname)
388 (file-regular-p fname))
389 (mailcap-parse-mailcap fname))
390 (setq fnames (cdr fnames))))
391 (setq mailcap-parsed-p t)))
392
393(defun mailcap-parse-mailcap (fname)
394 "Parse out the mailcap file specified by FNAME."
395 (let (major ; The major mime type (image/audio/etc)
396 minor ; The minor mime type (gif, basic, etc)
397 save-pos ; Misc saved positions used in parsing
398 viewer ; How to view this mime type
399 info ; Misc info about this mime type
400 )
401 (with-temp-buffer
402 (insert-file-contents fname)
403 (set-syntax-table mailcap-parse-args-syntax-table)
404 (mailcap-replace-regexp "#.*" "") ; Remove all comments
405 (mailcap-replace-regexp "\\\\[ \t]*\n" " ") ; And collapse spaces
406 (mailcap-replace-regexp "\n+" "\n") ; And blank lines
407 (goto-char (point-max))
408 (skip-chars-backward " \t\n")
409 (delete-region (point) (point-max))
410 (while (not (bobp))
411 (skip-chars-backward " \t\n")
412 (beginning-of-line)
413 (setq save-pos (point)
414 info nil)
415 (skip-chars-forward "^/; \t\n")
416 (downcase-region save-pos (point))
417 (setq major (buffer-substring save-pos (point)))
418 (skip-chars-forward " \t")
419 (setq minor "")
420 (when (eq (char-after) ?/)
421 (forward-char)
422 (skip-chars-forward " \t")
423 (setq save-pos (point))
424 (skip-chars-forward "^; \t\n")
425 (downcase-region save-pos (point))
426 (setq minor
427 (cond
428 ((eq ?* (or (char-after save-pos) 0)) ".*")
429 ((= (point) save-pos) ".*")
430 (t (regexp-quote (buffer-substring save-pos (point)))))))
431 (skip-chars-forward " \t")
432 ;;; Got the major/minor chunks, now for the viewers/etc
433 ;;; The first item _must_ be a viewer, according to the
434 ;;; RFC for mailcap files (#1524)
435 (setq viewer "")
436 (when (eq (char-after) ?\;)
437 (forward-char)
438 (skip-chars-forward " \t")
439 (setq save-pos (point))
440 (skip-chars-forward "^;\n")
441 ;; skip \;
442 (while (eq (char-before) ?\\)
443 (backward-delete-char 1)
444 (forward-char)
445 (skip-chars-forward "^;\n"))
446 (if (eq (or (char-after save-pos) 0) ?')
447 (setq viewer (progn
448 (narrow-to-region (1+ save-pos) (point))
449 (goto-char (point-min))
450 (prog1
451 (read (current-buffer))
452 (goto-char (point-max))
453 (widen))))
454 (setq viewer (buffer-substring save-pos (point)))))
455 (setq save-pos (point))
456 (end-of-line)
457 (unless (equal viewer "")
458 (setq info (nconc (list (cons 'viewer viewer)
459 (cons 'type (concat major "/"
460 (if (string= minor ".*")
461 "*" minor))))
462 (mailcap-parse-mailcap-extras save-pos (point))))
463 (mailcap-mailcap-entry-passes-test info)
464 (mailcap-add-mailcap-entry major minor info))
465 (beginning-of-line)))))
466
467(defun mailcap-parse-mailcap-extras (st nd)
468 "Grab all the extra stuff from a mailcap entry."
469 (let (
470 name ; From name=
471 value ; its value
472 results ; Assoc list of results
473 name-pos ; Start of XXXX= position
474 val-pos ; Start of value position
475 done ; Found end of \'d ;s?
476 )
477 (save-restriction
478 (narrow-to-region st nd)
479 (goto-char (point-min))
480 (skip-chars-forward " \n\t;")
481 (while (not (eobp))
482 (setq done nil)
483 (setq name-pos (point))
484 (skip-chars-forward "^ \n\t=;")
485 (downcase-region name-pos (point))
486 (setq name (buffer-substring name-pos (point)))
487 (skip-chars-forward " \t\n")
488 (if (not (eq (char-after (point)) ?=)) ; There is no value
489 (setq value t)
490 (skip-chars-forward " \t\n=")
491 (setq val-pos (point))
492 (if (memq (char-after val-pos) '(?\" ?'))
493 (progn
494 (setq val-pos (1+ val-pos))
495 (condition-case nil
496 (progn
497 (forward-sexp 1)
498 (backward-char 1))
499 (error (goto-char (point-max)))))
500 (while (not done)
501 (skip-chars-forward "^;")
502 (if (eq (char-after (1- (point))) ?\\ )
503 (progn
504 (subst-char-in-region (1- (point)) (point) ?\\ ? )
505 (skip-chars-forward ";"))
506 (setq done t))))
507 (setq value (buffer-substring val-pos (point))))
508 ;; `test' as symbol, others like "copiousoutput" and "needsx11" as
509 ;; strings
510 (setq results (cons (cons (if (string-equal name "test")
511 'test
512 name)
513 value) results))
514 (skip-chars-forward " \";\n\t"))
515 results)))
516
517(defun mailcap-mailcap-entry-passes-test (info)
518 "Replace the test clause of INFO itself with a boolean for some cases.
519This function supports only `test -n $DISPLAY' and `test -z $DISPLAY',
520replaces them with t or nil. As for others or if INFO has a interactive
521spec (needsterm, needsterminal, or needsx11) but DISPLAY is not set,
522the test clause will be unchanged."
523 (let ((test (assq 'test info)) ; The test clause
524 status)
525 (setq status (and test (split-string (cdr test) " ")))
526 (if (and (or (assoc "needsterm" info)
527 (assoc "needsterminal" info)
528 (assoc "needsx11" info))
529 (not (getenv "DISPLAY")))
530 (setq status nil)
531 (cond
532 ((and (equal (nth 0 status) "test")
533 (equal (nth 1 status) "-n")
534 (or (equal (nth 2 status) "$DISPLAY")
535 (equal (nth 2 status) "\"$DISPLAY\"")))
536 (setq status (if (getenv "DISPLAY") t nil)))
537 ((and (equal (nth 0 status) "test")
538 (equal (nth 1 status) "-z")
539 (or (equal (nth 2 status) "$DISPLAY")
540 (equal (nth 2 status) "\"$DISPLAY\"")))
541 (setq status (if (getenv "DISPLAY") nil t)))
542 (test nil)
543 (t nil)))
544 (and test (listp test) (setcdr test status))))
545
546;;;
547;;; The action routines.
548;;;
549
550(defun mailcap-possible-viewers (major minor)
551 "Return a list of possible viewers from MAJOR for minor type MINOR."
552 (let ((exact '())
553 (wildcard '()))
554 (while major
555 (cond
556 ((equal (car (car major)) minor)
557 (setq exact (cons (cdr (car major)) exact)))
558 ((and minor (string-match (concat "^" (car (car major)) "$") minor))
559 (setq wildcard (cons (cdr (car major)) wildcard))))
560 (setq major (cdr major)))
561 (nconc exact wildcard)))
562
563(defun mailcap-unescape-mime-test (test type-info)
564 (let (save-pos save-chr subst)
565 (cond
566 ((symbolp test) test)
567 ((and (listp test) (symbolp (car test))) test)
568 ((or (stringp test)
569 (and (listp test) (stringp (car test))
570 (setq test (mapconcat 'identity test " "))))
571 (with-temp-buffer
572 (insert test)
573 (goto-char (point-min))
574 (while (not (eobp))
575 (skip-chars-forward "^%")
576 (if (/= (- (point)
577 (progn (skip-chars-backward "\\\\")
578 (point)))
579 0) ; It is an escaped %
580 (progn
581 (delete-char 1)
582 (skip-chars-forward "%."))
583 (setq save-pos (point))
584 (skip-chars-forward "%")
585 (setq save-chr (char-after (point)))
586 ;; Escapes:
587 ;; %s: name of a file for the body data
588 ;; %t: content-type
589 ;; %{<parameter name}: value of parameter in mailcap entry
590 ;; %n: number of sub-parts for multipart content-type
591 ;; %F: a set of content-type/filename pairs for multiparts
592 (cond
593 ((null save-chr) nil)
594 ((= save-chr ?t)
595 (delete-region save-pos (progn (forward-char 1) (point)))
596 (insert (or (cdr (assq 'type type-info)) "\"\"")))
597 ((memq save-chr '(?M ?n ?F))
598 (delete-region save-pos (progn (forward-char 1) (point)))
599 (insert "\"\""))
600 ((= save-chr ?{)
601 (forward-char 1)
602 (skip-chars-forward "^}")
603 (downcase-region (+ 2 save-pos) (point))
604 (setq subst (buffer-substring (+ 2 save-pos) (point)))
605 (delete-region save-pos (1+ (point)))
606 (insert (or (cdr (assoc subst type-info)) "\"\"")))
607 (t nil))))
608 (buffer-string)))
609 (t (error "Bad value to mailcap-unescape-mime-test: %s" test)))))
610
611(defvar mailcap-viewer-test-cache nil)
612
613(defun mailcap-viewer-passes-test (viewer-info type-info)
614 "Return non-nil if viewer specified by VIEWER-INFO passes its test clause.
615Also return non-nil if it has no test clause. TYPE-INFO is an argument
616to supply to the test."
617 (let* ((test-info (assq 'test viewer-info))
618 (test (cdr test-info))
619 (otest test)
620 (viewer (cdr (assq 'viewer viewer-info)))
621 (default-directory (expand-file-name "~/"))
622 status parsed-test cache result)
623 (cond ((not (or (stringp viewer) (fboundp viewer)))
624 nil) ; Non-existent Lisp function
625 ((setq cache (assoc test mailcap-viewer-test-cache))
626 (cadr cache))
627 ((not test-info) t) ; No test clause
628 (t
629 (setq
630 result
631 (cond
632 ((not test) nil) ; Already failed test
633 ((eq test t) t) ; Already passed test
634 ((functionp test) ; Lisp function as test
635 (funcall test type-info))
636 ((and (symbolp test) ; Lisp variable as test
637 (boundp test))
638 (symbol-value test))
639 ((and (listp test) ; List to be eval'd
640 (symbolp (car test)))
641 (eval test))
642 (t
643 (setq test (mailcap-unescape-mime-test test type-info)
644 test (list shell-file-name nil nil nil
645 shell-command-switch test)
646 status (apply 'call-process test))
647 (eq 0 status))))
648 (push (list otest result) mailcap-viewer-test-cache)
649 result))))
650
651(defun mailcap-add-mailcap-entry (major minor info)
652 (let ((old-major (assoc major mailcap-mime-data)))
653 (if (null old-major) ; New major area
654 (setq mailcap-mime-data
655 (cons (cons major (list (cons minor info)))
656 mailcap-mime-data))
657 (let ((cur-minor (assoc minor old-major)))
658 (cond
659 ((or (null cur-minor) ; New minor area, or
660 (assq 'test info)) ; Has a test, insert at beginning
661 (setcdr old-major (cons (cons minor info) (cdr old-major))))
662 ((and (not (assq 'test info)) ; No test info, replace completely
663 (not (assq 'test cur-minor))
664 (equal (assq 'viewer info) ; Keep alternative viewer
665 (assq 'viewer cur-minor)))
666 (setcdr cur-minor info))
667 (t
668 (setcdr old-major (cons (cons minor info) (cdr old-major))))))
669 )))
670
671(defun mailcap-add (type viewer &optional test)
672 "Add VIEWER as a handler for TYPE.
673If TEST is not given, it defaults to t."
674 (let ((tl (split-string type "/")))
675 (when (or (not (car tl))
676 (not (cadr tl)))
677 (error "%s is not a valid MIME type" type))
678 (mailcap-add-mailcap-entry
679 (car tl) (cadr tl)
680 `((viewer . ,viewer)
681 (test . ,(if test test t))
682 (type . ,type)))))
683
684;;;
685;;; The main whabbo
686;;;
687
688(defun mailcap-viewer-lessp (x y)
689 "Return t if viewer X is more desirable than viewer Y."
690 (let ((x-wild (string-match "[*?]" (or (cdr-safe (assq 'type x)) "")))
691 (y-wild (string-match "[*?]" (or (cdr-safe (assq 'type y)) "")))
692 (x-lisp (not (stringp (or (cdr-safe (assq 'viewer x)) ""))))
693 (y-lisp (not (stringp (or (cdr-safe (assq 'viewer y)) "")))))
694 (cond
695 ((and x-wild (not y-wild))
696 nil)
697 ((and (not x-wild) y-wild)
698 t)
699 ((and (not y-lisp) x-lisp)
700 t)
701 (t nil))))
702
703(defun mailcap-mime-info (string &optional request no-decode)
704 "Get the MIME viewer command for STRING, return nil if none found.
705Expects a complete content-type header line as its argument.
706
707Second argument REQUEST specifies what information to return. If it is
708nil or the empty string, the viewer (second field of the mailcap
709entry) will be returned. If it is a string, then the mailcap field
710corresponding to that string will be returned (print, description,
711whatever). If a number, then all the information for this specific
712viewer is returned. If `all', then all possible viewers for
713this type is returned.
714
715If NO-DECODE is non-nil, don't decode STRING."
716 ;; NO-DECODE avoids calling `mail-header-parse-content-type' from
717 ;; `mail-parse.el'
718 (let (
719 major ; Major encoding (text, etc)
720 minor ; Minor encoding (html, etc)
721 info ; Other info
722 save-pos ; Misc. position during parse
723 major-info ; (assoc major mailcap-mime-data)
724 minor-info ; (assoc minor major-info)
725 test ; current test proc.
726 viewers ; Possible viewers
727 passed ; Viewers that passed the test
728 viewer ; The one and only viewer
729 ctl)
730 (save-excursion
731 (setq ctl
732 (if no-decode
733 (list (or string "text/plain"))
734 (mail-header-parse-content-type (or string "text/plain"))))
735 (setq major (split-string (car ctl) "/"))
736 (setq minor (cadr major)
737 major (car major))
738 (when (setq major-info (cdr (assoc major mailcap-mime-data)))
739 (when (setq viewers (mailcap-possible-viewers major-info minor))
740 (setq info (mapcar (lambda (a) (cons (symbol-name (car a))
741 (cdr a)))
742 (cdr ctl)))
743 (while viewers
744 (if (mailcap-viewer-passes-test (car viewers) info)
745 (setq passed (cons (car viewers) passed)))
746 (setq viewers (cdr viewers)))
747 (setq passed (sort passed 'mailcap-viewer-lessp))
748 (setq viewer (car passed))))
749 (when (and (stringp (cdr (assq 'viewer viewer)))
750 passed)
751 (setq viewer (car passed)))
752 (cond
753 ((and (null viewer) (not (equal major "default")) request)
754 (mailcap-mime-info "default" request no-decode))
755 ((or (null request) (equal request ""))
756 (mailcap-unescape-mime-test (cdr (assq 'viewer viewer)) info))
757 ((stringp request)
758 (mailcap-unescape-mime-test
759 (cdr-safe (assoc request viewer)) info))
760 ((eq request 'all)
761 passed)
762 (t
763 ;; MUST make a copy *sigh*, else we modify mailcap-mime-data
764 (setq viewer (copy-sequence viewer))
765 (let ((view (assq 'viewer viewer))
766 (test (assq 'test viewer)))
767 (if view (setcdr view (mailcap-unescape-mime-test (cdr view) info)))
768 (if test (setcdr test (mailcap-unescape-mime-test (cdr test) info))))
769 viewer)))))
770
771;;;
772;;; Experimental MIME-types parsing
773;;;
774
775(defvar mailcap-mime-extensions
776 '(("" . "text/plain")
777 (".1" . "text/plain") ;; Manual pages
778 (".3" . "text/plain")
779 (".8" . "text/plain")
780 (".abs" . "audio/x-mpeg")
781 (".aif" . "audio/aiff")
782 (".aifc" . "audio/aiff")
783 (".aiff" . "audio/aiff")
784 (".ano" . "application/x-annotator")
785 (".au" . "audio/ulaw")
786 (".avi" . "video/x-msvideo")
787 (".bcpio" . "application/x-bcpio")
788 (".bin" . "application/octet-stream")
789 (".cdf" . "application/x-netcdr")
790 (".cpio" . "application/x-cpio")
791 (".csh" . "application/x-csh")
792 (".css" . "text/css")
793 (".dvi" . "application/x-dvi")
794 (".diff" . "text/x-patch")
795 (".dpatch". "test/x-patch")
796 (".el" . "application/emacs-lisp")
797 (".eps" . "application/postscript")
798 (".etx" . "text/x-setext")
799 (".exe" . "application/octet-stream")
800 (".fax" . "image/x-fax")
801 (".gif" . "image/gif")
802 (".hdf" . "application/x-hdf")
803 (".hqx" . "application/mac-binhex40")
804 (".htm" . "text/html")
805 (".html" . "text/html")
806 (".icon" . "image/x-icon")
807 (".ief" . "image/ief")
808 (".jpg" . "image/jpeg")
809 (".macp" . "image/x-macpaint")
810 (".man" . "application/x-troff-man")
811 (".me" . "application/x-troff-me")
812 (".mif" . "application/mif")
813 (".mov" . "video/quicktime")
814 (".movie" . "video/x-sgi-movie")
815 (".mp2" . "audio/x-mpeg")
816 (".mp3" . "audio/x-mpeg")
817 (".mp2a" . "audio/x-mpeg2")
818 (".mpa" . "audio/x-mpeg")
819 (".mpa2" . "audio/x-mpeg2")
820 (".mpe" . "video/mpeg")
821 (".mpeg" . "video/mpeg")
822 (".mpega" . "audio/x-mpeg")
823 (".mpegv" . "video/mpeg")
824 (".mpg" . "video/mpeg")
825 (".mpv" . "video/mpeg")
826 (".ms" . "application/x-troff-ms")
827 (".nc" . "application/x-netcdf")
828 (".nc" . "application/x-netcdf")
829 (".oda" . "application/oda")
830 (".patch" . "text/x-patch")
831 (".pbm" . "image/x-portable-bitmap")
832 (".pdf" . "application/pdf")
833 (".pgm" . "image/portable-graymap")
834 (".pict" . "image/pict")
835 (".png" . "image/png")
836 (".pnm" . "image/x-portable-anymap")
837 (".pod" . "text/plain")
838 (".ppm" . "image/portable-pixmap")
839 (".ps" . "application/postscript")
840 (".qt" . "video/quicktime")
841 (".ras" . "image/x-raster")
842 (".rgb" . "image/x-rgb")
843 (".rtf" . "application/rtf")
844 (".rtx" . "text/richtext")
845 (".sh" . "application/x-sh")
846 (".sit" . "application/x-stuffit")
847 (".siv" . "application/sieve")
848 (".snd" . "audio/basic")
849 (".soa" . "text/dns")
850 (".src" . "application/x-wais-source")
851 (".tar" . "archive/tar")
852 (".tcl" . "application/x-tcl")
853 (".tex" . "application/x-tex")
854 (".texi" . "application/texinfo")
855 (".tga" . "image/x-targa")
856 (".tif" . "image/tiff")
857 (".tiff" . "image/tiff")
858 (".tr" . "application/x-troff")
859 (".troff" . "application/x-troff")
860 (".tsv" . "text/tab-separated-values")
861 (".txt" . "text/plain")
862 (".vbs" . "video/mpeg")
863 (".vox" . "audio/basic")
864 (".vrml" . "x-world/x-vrml")
865 (".wav" . "audio/x-wav")
866 (".xls" . "application/vnd.ms-excel")
867 (".wrl" . "x-world/x-vrml")
868 (".xbm" . "image/xbm")
869 (".xpm" . "image/xpm")
870 (".xwd" . "image/windowdump")
871 (".zip" . "application/zip")
872 (".ai" . "application/postscript")
873 (".jpe" . "image/jpeg")
874 (".jpeg" . "image/jpeg")
875 (".org" . "text/x-org"))
876 "An alist of file extensions and corresponding MIME content-types.
877This exists for you to customize the information in Lisp. It is
878merged with values from mailcap files by `mailcap-parse-mimetypes'.")
879
880(defvar mailcap-mimetypes-parsed-p nil)
881
882(defun mailcap-parse-mimetypes (&optional path force)
883 "Parse out all the mimetypes specified in a Unix-style path string PATH.
884Components of PATH are separated by the `path-separator' character
885appropriate for this system. If PATH is omitted, use the value of
886environment variable MIMETYPES if set; otherwise use a default path.
887If FORCE, re-parse even if already parsed."
888 (interactive (list nil t))
889 (when (or (not mailcap-mimetypes-parsed-p)
890 force)
891 (cond
892 (path nil)
893 ((getenv "MIMETYPES") (setq path (getenv "MIMETYPES")))
894 ((memq system-type mailcap-poor-system-types)
895 (setq path '("~/mime.typ" "~/etc/mime.typ")))
896 (t (setq path
897 ;; mime.types seems to be the normal name, definitely so
898 ;; on current GNUish systems. The search order follows
899 ;; that for mailcap.
900 '("~/.mime.types"
901 "/etc/mime.types"
902 "/usr/etc/mime.types"
903 "/usr/local/etc/mime.types"
904 "/usr/local/www/conf/mime.types"
905 "~/.mime-types"
906 "/etc/mime-types"
907 "/usr/etc/mime-types"
908 "/usr/local/etc/mime-types"
909 "/usr/local/www/conf/mime-types"))))
910 (let ((fnames (reverse (if (stringp path)
911 (split-string path path-separator t)
912 path)))
913 fname)
914 (while fnames
915 (setq fname (car fnames))
916 (if (and (file-readable-p fname))
917 (mailcap-parse-mimetype-file fname))
918 (setq fnames (cdr fnames))))
919 (setq mailcap-mimetypes-parsed-p t)))
920
921(defun mailcap-parse-mimetype-file (fname)
922 "Parse out a mime-types file FNAME."
923 (let (type ; The MIME type for this line
924 extns ; The extensions for this line
925 save-pos ; Misc. saved buffer positions
926 )
927 (with-temp-buffer
928 (insert-file-contents fname)
929 (mailcap-replace-regexp "#.*" "")
930 (mailcap-replace-regexp "\n+" "\n")
931 (mailcap-replace-regexp "[ \t]+$" "")
932 (goto-char (point-max))
933 (skip-chars-backward " \t\n")
934 (delete-region (point) (point-max))
935 (goto-char (point-min))
936 (while (not (eobp))
937 (skip-chars-forward " \t\n")
938 (setq save-pos (point))
939 (skip-chars-forward "^ \t\n")
940 (downcase-region save-pos (point))
941 (setq type (buffer-substring save-pos (point)))
942 (while (not (eolp))
943 (skip-chars-forward " \t")
944 (setq save-pos (point))
945 (skip-chars-forward "^ \t\n")
946 (setq extns (cons (buffer-substring save-pos (point)) extns)))
947 (while extns
948 (setq mailcap-mime-extensions
949 (cons
950 (cons (if (= (string-to-char (car extns)) ?.)
951 (car extns)
952 (concat "." (car extns))) type)
953 mailcap-mime-extensions)
954 extns (cdr extns)))))))
955
956(defun mailcap-extension-to-mime (extn)
957 "Return the MIME content type of the file extensions EXTN."
958 (mailcap-parse-mimetypes)
959 (if (and (stringp extn)
960 (not (eq (string-to-char extn) ?.)))
961 (setq extn (concat "." extn)))
962 (cdr (assoc (downcase extn) mailcap-mime-extensions)))
963
964;; Unused?
965(defalias 'mailcap-command-p 'executable-find)
966
967(defun mailcap-mime-types ()
968 "Return a list of MIME media types."
969 (mailcap-parse-mimetypes)
970 (delete-dups
971 (nconc
972 (mapcar 'cdr mailcap-mime-extensions)
973 (apply
974 'nconc
975 (mapcar
976 (lambda (l)
977 (delq nil
978 (mapcar
979 (lambda (m)
980 (let ((type (cdr (assq 'type (cdr m)))))
981 (if (equal (cadr (split-string type "/"))
982 "*")
983 nil
984 type)))
985 (cdr l))))
986 mailcap-mime-data)))))
987
988;;;
989;;; Useful supplementary functions
990;;;
991
992(defun mailcap-file-default-commands (files)
993 "Return a list of default commands for FILES."
994 (mailcap-parse-mailcaps)
995 (mailcap-parse-mimetypes)
996 (let* ((all-mime-type
997 ;; All unique MIME types from file extensions
998 (delete-dups
999 (mapcar (lambda (file)
1000 (mailcap-extension-to-mime
1001 (file-name-extension file t)))
1002 files)))
1003 (all-mime-info
1004 ;; All MIME info lists
1005 (delete-dups
1006 (mapcar (lambda (mime-type)
1007 (mailcap-mime-info mime-type 'all))
1008 all-mime-type)))
1009 (common-mime-info
1010 ;; Intersection of mime-infos from different mime-types;
1011 ;; or just the first MIME info for a single MIME type
1012 (if (cdr all-mime-info)
1013 (delq nil (mapcar (lambda (mi1)
1014 (unless (memq nil (mapcar
1015 (lambda (mi2)
1016 (member mi1 mi2))
1017 (cdr all-mime-info)))
1018 mi1))
1019 (car all-mime-info)))
1020 (car all-mime-info)))
1021 (commands
1022 ;; Command strings from `viewer' field of the MIME info
1023 (delete-dups
1024 (delq nil (mapcar
1025 (lambda (mime-info)
1026 (let ((command (cdr (assoc 'viewer mime-info))))
1027 (if (stringp command)
1028 (replace-regexp-in-string
1029 ;; Replace mailcap's `%s' placeholder
1030 ;; with dired's `?' placeholder
1031 "%s" "?"
1032 (replace-regexp-in-string
1033 ;; Remove the final filename placeholder
1034 "[ \t\n]*\\('\\)?%s\\1?[ \t\n]*\\'" ""
1035 command nil t)
1036 nil t))))
1037 common-mime-info)))))
1038 commands))
1039
1040(defun mailcap-view-mime (type)
1041 "View the data in the current buffer that has MIME type TYPE.
1042`mailcap-mime-data' determines the method to use."
1043 (let ((method (mailcap-mime-info type)))
1044 (if (stringp method)
1045 (shell-command-on-region (point-min) (point-max)
1046 ;; Use stdin as the "%s".
1047 (format method "-")
1048 (current-buffer)
1049 t)
1050 (funcall method))))
1051
1052(provide 'mailcap)
1053
1054;;; mailcap.el ends here
diff --git a/lisp/net/pop3.el b/lisp/net/pop3.el
new file mode 100644
index 00000000000..1695bbd3a40
--- /dev/null
+++ b/lisp/net/pop3.el
@@ -0,0 +1,914 @@
1;;; pop3.el --- Post Office Protocol (RFC 1460) interface
2
3;; Copyright (C) 1996-2016 Free Software Foundation, Inc.
4
5;; Author: Richard L. Pieri <ratinox@peorth.gweep.net>
6;; Maintainer: emacs-devel@gnu.org
7;; Keywords: mail
8
9;; This file is part of GNU Emacs.
10
11;; GNU Emacs is free software: you can redistribute it and/or modify
12;; it under the terms of the GNU General Public License as published by
13;; the Free Software Foundation, either version 3 of the License, or
14;; (at your option) any later version.
15
16;; GNU Emacs is distributed in the hope that it will be useful,
17;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19;; GNU General Public License for more details.
20
21;; You should have received a copy of the GNU General Public License
22;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
23
24;;; Commentary:
25
26;; Most of the standard Post Office Protocol version 3 (RFC 1460) commands
27;; are implemented. The LIST command has not been implemented due to lack
28;; of actual usefulness.
29;; The optional POP3 command TOP has not been implemented.
30
31;; This program was inspired by Kyle E. Jones's vm-pop program.
32
33;;; Code:
34
35(eval-when-compile (require 'cl))
36
37(require 'mail-utils)
38(defvar parse-time-months)
39
40(defgroup pop3 nil
41 "Post Office Protocol."
42 :group 'mail
43 :group 'mail-source)
44
45(defcustom pop3-maildrop (or (user-login-name)
46 (getenv "LOGNAME")
47 (getenv "USER"))
48 "*POP3 maildrop."
49 :version "22.1" ;; Oort Gnus
50 :type 'string
51 :group 'pop3)
52
53(defcustom pop3-mailhost (or (getenv "MAILHOST") ;; nil -> mismatch
54 "pop3")
55 "*POP3 mailhost."
56 :version "22.1" ;; Oort Gnus
57 :type 'string
58 :group 'pop3)
59
60(defcustom pop3-port 110
61 "*POP3 port."
62 :version "22.1" ;; Oort Gnus
63 :type 'number
64 :group 'pop3)
65
66(defcustom pop3-password-required t
67 "*Non-nil if a password is required when connecting to POP server."
68 :version "22.1" ;; Oort Gnus
69 :type 'boolean
70 :group 'pop3)
71
72;; Should this be customizable?
73(defvar pop3-password nil
74 "*Password to use when connecting to POP server.")
75
76(defcustom pop3-authentication-scheme 'pass
77 "*POP3 authentication scheme.
78Defaults to `pass', for the standard USER/PASS authentication. The other
79valid value is `apop'."
80 :type '(choice (const :tag "Normal user/password" pass)
81 (const :tag "APOP" apop))
82 :version "22.1" ;; Oort Gnus
83 :group 'pop3)
84
85(defcustom pop3-stream-length 100
86 "How many messages should be requested at one time.
87The lower the number, the more latency-sensitive the fetching
88will be. If your pop3 server doesn't support streaming at all,
89set this to 1."
90 :type 'number
91 :version "24.1"
92 :group 'pop3)
93
94(defcustom pop3-leave-mail-on-server nil
95 "Non-nil if the mail is to be left on the POP server after fetching.
96Mails once fetched will never be fetched again by the UIDL control.
97
98If this is neither nil nor a number, all mails will be left on the
99server. If this is a number, leave mails on the server for this many
100days since you first checked new mails. If this is nil, mails will be
101deleted on the server right after fetching.
102
103Gnus users should use the `:leave' keyword in a mail source to direct
104the behavior per server, rather than directly modifying this value.
105
106Note that POP servers maintain no state information between sessions,
107so what the client believes is there and what is actually there may
108not match up. If they do not, then you may get duplicate mails or
109the whole thing can fall apart and leave you with a corrupt mailbox."
110 :version "24.4"
111 :type '(choice (const :tag "Don't leave mails" nil)
112 (const :tag "Leave all mails" t)
113 (number :tag "Leave mails for this many days" :value 14))
114 :group 'pop3)
115
116(defcustom pop3-uidl-file "~/.pop3-uidl"
117 "File used to save UIDL."
118 :version "24.4"
119 :type 'file
120 :group 'pop3)
121
122(defcustom pop3-uidl-file-backup '(0 9)
123 "How to backup the UIDL file `pop3-uidl-file' when updating.
124If it is a list of numbers, the first one binds `kept-old-versions' and
125the other binds `kept-new-versions' to keep number of oldest and newest
126versions. Otherwise, the value binds `version-control' (which see).
127
128Note: Backup will take place whenever you check new mails on a server.
129So, you may lose the backup files having been saved before a trouble
130if you set it so as to make too few backups whereas you have access to
131many servers."
132 :version "24.4"
133 :type '(choice (group :tag "Keep versions" :format "\n%v" :indent 3
134 (number :tag "oldest")
135 (number :tag "newest"))
136 (sexp :format "%v"
137 :match (lambda (widget value)
138 (condition-case nil
139 (not (and (numberp (car value))
140 (numberp (car (cdr value)))))
141 (error t)))))
142 :group 'pop3)
143
144(defvar pop3-timestamp nil
145 "Timestamp returned when initially connected to the POP server.
146Used for APOP authentication.")
147
148(defvar pop3-read-point nil)
149(defvar pop3-debug nil)
150
151;; Borrowed from nnheader-accept-process-output in nnheader.el. See the
152;; comments there for explanations about the values.
153
154(eval-and-compile
155 (if (and (fboundp 'nnheader-accept-process-output)
156 (boundp 'nnheader-read-timeout))
157 (defalias 'pop3-accept-process-output 'nnheader-accept-process-output)
158 ;; Borrowed from `nnheader.el':
159 (defvar pop3-read-timeout
160 (if (string-match "windows-nt\\|os/2\\|cygwin"
161 (symbol-name system-type))
162 1.0
163 0.01)
164 "How long pop3 should wait between checking for the end of output.
165Shorter values mean quicker response, but are more CPU intensive.")
166 (defun pop3-accept-process-output (process)
167 (accept-process-output
168 process
169 (truncate pop3-read-timeout)
170 (truncate (* (- pop3-read-timeout
171 (truncate pop3-read-timeout))
172 1000))))))
173
174(defvar pop3-uidl)
175;; List of UIDLs of existing messages at present in the server:
176;; ("UIDL1" "UIDL2" "UIDL3"...)
177
178(defvar pop3-uidl-saved)
179;; Locally saved UIDL data; an alist of the server, the user, and the UIDL
180;; and timestamp pairs:
181;; (("SERVER_A" ("USER_A1" "UIDL1" TIMESTAMP1 "UIDL2" TIMESTAMP2...)
182;; ("USER_A2" "UIDL1" TIMESTAMP1 "UIDL2" TIMESTAMP2...)
183;; ...)
184;; ("SERVER_B" ("USER_B1" "UIDL1" TIMESTAMP1 "UIDL2" TIMESTAMP2...)
185;; ("USER_B2" "UIDL1" TIMESTAMP1 "UIDL2" TIMESTAMP2...)
186;; ...))
187;; Where TIMESTAMP is the most significant two digits of an Emacs time,
188;; i.e. the return value of `current-time'.
189
190;;;###autoload
191(defun pop3-movemail (file)
192 "Transfer contents of a maildrop to the specified FILE.
193Use streaming commands."
194 (let ((process (pop3-open-server pop3-mailhost pop3-port))
195 messages total-size
196 pop3-uidl
197 pop3-uidl-saved)
198 (pop3-logon process)
199 (if pop3-leave-mail-on-server
200 (setq messages (pop3-uidl-stat process)
201 total-size (cadr messages)
202 messages (car messages))
203 (let ((size (pop3-stat process)))
204 (dotimes (i (car size)) (push (1+ i) messages))
205 (setq messages (nreverse messages)
206 total-size (cadr size))))
207 (when messages
208 (with-current-buffer (process-buffer process)
209 (pop3-send-streaming-command process "RETR" messages total-size)
210 (pop3-write-to-file file messages)
211 (unless pop3-leave-mail-on-server
212 (pop3-send-streaming-command process "DELE" messages nil))))
213 (if pop3-leave-mail-on-server
214 (when (prog1 (pop3-uidl-dele process) (pop3-quit process))
215 (pop3-uidl-save))
216 (pop3-quit process)
217 ;; Remove UIDL data for the account that got not to leave mails.
218 (setq pop3-uidl-saved (pop3-uidl-load))
219 (let ((elt (assoc pop3-maildrop
220 (cdr (assoc pop3-mailhost pop3-uidl-saved)))))
221 (when elt
222 (setcdr elt nil)
223 (pop3-uidl-save))))
224 t))
225
226(defun pop3-send-streaming-command (process command messages total-size)
227 (erase-buffer)
228 (let ((count (length messages))
229 (i 1)
230 (start-point (point-min))
231 (waited-for 0))
232 (while messages
233 (process-send-string process (format "%s %d\r\n" command (pop messages)))
234 ;; Only do 100 messages at a time to avoid pipe stalls.
235 (when (zerop (% i pop3-stream-length))
236 (setq start-point
237 (pop3-wait-for-messages process pop3-stream-length
238 total-size start-point))
239 (incf waited-for pop3-stream-length))
240 (incf i))
241 (pop3-wait-for-messages process (- count waited-for)
242 total-size start-point)))
243
244(defun pop3-wait-for-messages (process count total-size start-point)
245 (while (> count 0)
246 (goto-char start-point)
247 (while (or (and (re-search-forward "^\\+OK" nil t)
248 (or (not total-size)
249 (re-search-forward "^\\.\r?\n" nil t)))
250 (re-search-forward "^-ERR " nil t))
251 (decf count)
252 (setq start-point (point)))
253 (unless (memq (process-status process) '(open run))
254 (error "pop3 process died"))
255 (when total-size
256 (let ((size 0))
257 (goto-char (point-min))
258 (while (re-search-forward "^\\+OK.*\n" nil t)
259 (setq size (+ size (- (point))
260 (if (re-search-forward "^\\.\r?\n" nil 'move)
261 (match-beginning 0)
262 (point)))))
263 (message "pop3 retrieved %dKB (%d%%)"
264 (truncate (/ size 1000))
265 (truncate (* (/ (* size 1.0) total-size) 100)))))
266 (pop3-accept-process-output process))
267 start-point)
268
269(defun pop3-write-to-file (file messages)
270 (let ((pop-buffer (current-buffer))
271 (start (point-min))
272 beg end
273 temp-buffer)
274 (with-temp-buffer
275 (setq temp-buffer (current-buffer))
276 (with-current-buffer pop-buffer
277 (goto-char (point-min))
278 (while (re-search-forward "^\\+OK" nil t)
279 (forward-line 1)
280 (setq beg (point))
281 (when (re-search-forward "^\\.\r?\n" nil t)
282 (setq start (point))
283 (forward-line -1)
284 (setq end (point)))
285 (with-current-buffer temp-buffer
286 (goto-char (point-max))
287 (let ((hstart (point)))
288 (insert-buffer-substring pop-buffer beg end)
289 (pop3-clean-region hstart (point))
290 (goto-char (point-max))
291 (pop3-munge-message-separator hstart (point))
292 (when pop3-leave-mail-on-server
293 (pop3-uidl-add-xheader hstart (pop messages)))
294 (goto-char (point-max))))))
295 (let ((coding-system-for-write 'binary))
296 (goto-char (point-min))
297 ;; Check whether something inserted a newline at the start and
298 ;; delete it.
299 (when (eolp)
300 (delete-char 1))
301 (write-region (point-min) (point-max) file nil 'nomesg)))))
302
303(defun pop3-logon (process)
304 (let ((pop3-password pop3-password))
305 ;; for debugging only
306 (if pop3-debug (switch-to-buffer (process-buffer process)))
307 ;; query for password
308 (if (and pop3-password-required (not pop3-password))
309 (setq pop3-password
310 (read-passwd (format "Password for %s: " pop3-maildrop))))
311 (cond ((equal 'apop pop3-authentication-scheme)
312 (pop3-apop process pop3-maildrop))
313 ((equal 'pass pop3-authentication-scheme)
314 (pop3-user process pop3-maildrop)
315 (pop3-pass process))
316 (t (error "Invalid POP3 authentication scheme")))))
317
318(defun pop3-get-message-count ()
319 "Return the number of messages in the maildrop."
320 (let* ((process (pop3-open-server pop3-mailhost pop3-port))
321 message-count
322 (pop3-password pop3-password))
323 ;; for debugging only
324 (if pop3-debug (switch-to-buffer (process-buffer process)))
325 ;; query for password
326 (if (and pop3-password-required (not pop3-password))
327 (setq pop3-password
328 (read-passwd (format "Password for %s: " pop3-maildrop))))
329 (cond ((equal 'apop pop3-authentication-scheme)
330 (pop3-apop process pop3-maildrop))
331 ((equal 'pass pop3-authentication-scheme)
332 (pop3-user process pop3-maildrop)
333 (pop3-pass process))
334 (t (error "Invalid POP3 authentication scheme")))
335 (setq message-count (car (pop3-stat process)))
336 (pop3-quit process)
337 message-count))
338
339(defun pop3-uidl-stat (process)
340 "Return a list of unread message numbers and total size."
341 (pop3-send-command process "UIDL")
342 (let (err messages size)
343 (if (condition-case code
344 (progn
345 (pop3-read-response process)
346 t)
347 (error (setq err (error-message-string code))
348 nil))
349 (let ((start pop3-read-point)
350 saved list)
351 (with-current-buffer (process-buffer process)
352 (while (not (re-search-forward "^\\.\r\n" nil t))
353 (unless (memq (process-status process) '(open run))
354 (error "pop3 server closed the connection"))
355 (pop3-accept-process-output process)
356 (goto-char start))
357 (setq pop3-read-point (point-marker)
358 pop3-uidl nil)
359 (while (progn (forward-line -1) (>= (point) start))
360 (when (looking-at "[0-9]+ \\([^\n\r ]+\\)")
361 (push (match-string 1) pop3-uidl)))
362 (when pop3-uidl
363 (setq pop3-uidl-saved (pop3-uidl-load)
364 saved (cdr (assoc pop3-maildrop
365 (cdr (assoc pop3-mailhost
366 pop3-uidl-saved)))))
367 (let ((i (length pop3-uidl)))
368 (while (> i 0)
369 (unless (member (nth (1- i) pop3-uidl) saved)
370 (push i messages))
371 (decf i)))
372 (when messages
373 (setq list (pop3-list process)
374 size 0)
375 (dolist (msg messages)
376 (setq size (+ size (cdr (assq msg list)))))
377 (list messages size)))))
378 (message "%s doesn't support UIDL (%s), so we try a regressive way..."
379 pop3-mailhost err)
380 (sit-for 1)
381 (setq size (pop3-stat process))
382 (dotimes (i (car size)) (push (1+ i) messages))
383 (setcar size (nreverse messages))
384 size)))
385
386(defun pop3-uidl-dele (process)
387 "Delete messages according to `pop3-leave-mail-on-server'.
388Return non-nil if it is necessary to update the local UIDL file."
389 (let* ((ctime (current-time))
390 (srvr (assoc pop3-mailhost pop3-uidl-saved))
391 (saved (assoc pop3-maildrop (cdr srvr)))
392 i uidl mod new tstamp dele)
393 (setcdr (cdr ctime) nil)
394 ;; Add new messages to the data to be saved.
395 (cond ((and pop3-uidl saved)
396 (setq i (1- (length pop3-uidl)))
397 (while (>= i 0)
398 (unless (member (setq uidl (nth i pop3-uidl)) (cdr saved))
399 (push ctime new)
400 (push uidl new))
401 (decf i)))
402 (pop3-uidl
403 (setq new (apply 'nconc (mapcar (lambda (elt) (list elt ctime))
404 pop3-uidl)))))
405 (when new (setq mod t))
406 ;; List expirable messages and delete them from the data to be saved.
407 (setq ctime (when (numberp pop3-leave-mail-on-server)
408 (/ (+ (* (car ctime) 65536.0) (cadr ctime)) 86400))
409 i (1- (length saved)))
410 (while (> i 0)
411 (if (member (setq uidl (nth (1- i) saved)) pop3-uidl)
412 (progn
413 (setq tstamp (nth i saved))
414 (if (and ctime
415 (> (- ctime (/ (+ (* (car tstamp) 65536.0) (cadr tstamp))
416 86400))
417 pop3-leave-mail-on-server))
418 ;; Mails to delete.
419 (progn
420 (setq mod t)
421 (push uidl dele))
422 ;; Mails to keep.
423 (push tstamp new)
424 (push uidl new)))
425 ;; Mails having been deleted in the server.
426 (setq mod t))
427 (decf i 2))
428 (cond (saved
429 (setcdr saved new))
430 (srvr
431 (setcdr (last srvr) (list (cons pop3-maildrop new))))
432 (t
433 (add-to-list 'pop3-uidl-saved
434 (list pop3-mailhost (cons pop3-maildrop new))
435 t)))
436 ;; Actually delete the messages in the server.
437 (when dele
438 (setq uidl nil
439 i (length pop3-uidl))
440 (while (> i 0)
441 (when (member (nth (1- i) pop3-uidl) dele)
442 (push i uidl))
443 (decf i))
444 (when uidl
445 (pop3-send-streaming-command process "DELE" uidl nil)))
446 mod))
447
448(defun pop3-uidl-load ()
449 "Load saved UIDL."
450 (when (file-exists-p pop3-uidl-file)
451 (with-temp-buffer
452 (condition-case code
453 (progn
454 (insert-file-contents pop3-uidl-file)
455 (goto-char (point-min))
456 (read (current-buffer)))
457 (error
458 (message "Error while loading %s (%s)"
459 pop3-uidl-file (error-message-string code))
460 (sit-for 1)
461 nil)))))
462
463(defun pop3-uidl-save ()
464 "Save UIDL."
465 (with-temp-buffer
466 (if pop3-uidl-saved
467 (progn
468 (insert "(")
469 (dolist (srvr pop3-uidl-saved)
470 (when (cdr srvr)
471 (insert "(\"" (pop srvr) "\"\n ")
472 (dolist (elt srvr)
473 (when (cdr elt)
474 (insert "(\"" (pop elt) "\"\n ")
475 (while elt
476 (insert (format "\"%s\" %s\n " (pop elt) (pop elt))))
477 (delete-char -4)
478 (insert ")\n ")))
479 (delete-char -3)
480 (if (eq (char-before) ?\))
481 (insert ")\n ")
482 (goto-char (1+ (point-at-bol)))
483 (delete-region (point) (point-max)))))
484 (when (eq (char-before) ? )
485 (delete-char -2))
486 (insert ")\n"))
487 (insert "()\n"))
488 (let ((buffer-file-name pop3-uidl-file)
489 (delete-old-versions t)
490 (kept-new-versions kept-new-versions)
491 (kept-old-versions kept-old-versions)
492 (version-control version-control))
493 (if (consp pop3-uidl-file-backup)
494 (setq kept-new-versions (cadr pop3-uidl-file-backup)
495 kept-old-versions (car pop3-uidl-file-backup)
496 version-control t)
497 (setq version-control pop3-uidl-file-backup))
498 (save-buffer))))
499
500(defun pop3-uidl-add-xheader (start msgno)
501 "Add X-UIDL header."
502 (let ((case-fold-search t))
503 (save-restriction
504 (narrow-to-region start (progn
505 (goto-char start)
506 (search-forward "\n\n" nil 'move)
507 (1- (point))))
508 (goto-char start)
509 (while (re-search-forward "^x-uidl:" nil t)
510 (while (progn
511 (forward-line 1)
512 (memq (char-after) '(?\t ? ))))
513 (delete-region (match-beginning 0) (point)))
514 (goto-char (point-max))
515 (insert "X-UIDL: " (nth (1- msgno) pop3-uidl) "\n"))))
516
517(defcustom pop3-stream-type nil
518 "*Transport security type for POP3 connections.
519This may be either nil (plain connection), `ssl' (use an
520SSL/TSL-secured stream) or `starttls' (use the starttls mechanism
521to turn on TLS security after opening the stream). However, if
522this is nil, `ssl' is assumed for connections to port
523995 (pop3s)."
524 :version "23.1" ;; No Gnus
525 :group 'pop3
526 :type '(choice (const :tag "Plain" nil)
527 (const :tag "SSL/TLS" ssl)
528 (const starttls)))
529
530(defun pop3-open-server (mailhost port)
531 "Open TCP connection to MAILHOST on PORT.
532Returns the process associated with the connection."
533 (let ((coding-system-for-read 'binary)
534 (coding-system-for-write 'binary)
535 result)
536 (with-current-buffer
537 (get-buffer-create (concat " trace of POP session to "
538 mailhost))
539 (erase-buffer)
540 (setq pop3-read-point (point-min))
541 (setq result
542 (open-network-stream
543 "POP" (current-buffer) mailhost port
544 :type (cond
545 ((or (eq pop3-stream-type 'ssl)
546 (and (not pop3-stream-type)
547 (member port '(995 "pop3s"))))
548 'tls)
549 (t
550 (or pop3-stream-type 'network)))
551 :warn-unless-encrypted t
552 :capability-command "CAPA\r\n"
553 :end-of-command "^\\(-ERR\\|+OK\\).*\n"
554 :end-of-capability "^\\.\r?\n\\|^-ERR"
555 :success "^\\+OK.*\n"
556 :return-list t
557 :starttls-function
558 (lambda (capabilities)
559 (and (string-match "\\bSTLS\\b" capabilities)
560 "STLS\r\n"))))
561 (when result
562 (let ((response (plist-get (cdr result) :greeting)))
563 (setq pop3-timestamp
564 (substring response (or (string-match "<" response) 0)
565 (+ 1 (or (string-match ">" response) -1)))))
566 (set-process-query-on-exit-flag (car result) nil)
567 (erase-buffer)
568 (car result)))))
569
570;; Support functions
571
572(defun pop3-send-command (process command)
573 (set-buffer (process-buffer process))
574 (goto-char (point-max))
575 ;; (if (= (aref command 0) ?P)
576 ;; (insert "PASS <omitted>\r\n")
577 ;; (insert command "\r\n"))
578 (setq pop3-read-point (point))
579 (goto-char (point-max))
580 (process-send-string process (concat command "\r\n")))
581
582(defun pop3-read-response (process &optional return)
583 "Read the response from the server.
584Return the response string if optional second argument is non-nil."
585 (let ((case-fold-search nil)
586 match-end)
587 (with-current-buffer (process-buffer process)
588 (goto-char pop3-read-point)
589 (while (and (memq (process-status process) '(open run))
590 (not (search-forward "\r\n" nil t)))
591 (pop3-accept-process-output process)
592 (goto-char pop3-read-point))
593 (setq match-end (point))
594 (goto-char pop3-read-point)
595 (if (looking-at "-ERR")
596 (error "%s" (buffer-substring (point) (- match-end 2)))
597 (if (not (looking-at "+OK"))
598 (progn (setq pop3-read-point match-end) nil)
599 (setq pop3-read-point match-end)
600 (if return
601 (buffer-substring (point) match-end)
602 t)
603 )))))
604
605(defun pop3-clean-region (start end)
606 (setq end (set-marker (make-marker) end))
607 (save-excursion
608 (goto-char start)
609 (while (and (< (point) end) (search-forward "\r\n" end t))
610 (replace-match "\n" t t))
611 (goto-char start)
612 (while (and (< (point) end) (re-search-forward "^\\." end t))
613 (replace-match "" t t)
614 (forward-char)))
615 (set-marker end nil))
616
617;; Copied from message-make-date.
618(defun pop3-make-date (&optional now)
619 "Make a valid date header.
620If NOW, use that time instead."
621 (require 'parse-time)
622 (let* ((now (or now (current-time)))
623 (zone (nth 8 (decode-time now)))
624 (sign "+"))
625 (when (< zone 0)
626 (setq sign "-")
627 (setq zone (- zone)))
628 (concat
629 (format-time-string "%d" now)
630 ;; The month name of the %b spec is locale-specific. Pfff.
631 (format " %s "
632 (capitalize (car (rassoc (nth 4 (decode-time now))
633 parse-time-months))))
634 (format-time-string "%Y %H:%M:%S %z" now))))
635
636(defun pop3-munge-message-separator (start end)
637 "Check to see if a message separator exists. If not, generate one."
638 (save-excursion
639 (save-restriction
640 (narrow-to-region start end)
641 (goto-char (point-min))
642 (if (not (or (looking-at "From .?") ; Unix mail
643 (looking-at "\001\001\001\001\n") ; MMDF
644 (looking-at "BABYL OPTIONS:") ; Babyl
645 ))
646 (let* ((from (mail-strip-quoted-names (mail-fetch-field "From")))
647 (tdate (mail-fetch-field "Date"))
648 (date (split-string (or (and tdate
649 (not (string= "" tdate))
650 tdate)
651 (pop3-make-date))
652 " "))
653 (From_))
654 ;; sample date formats I have seen
655 ;; Date: Tue, 9 Jul 1996 09:04:21 -0400 (EDT)
656 ;; Date: 08 Jul 1996 23:22:24 -0400
657 ;; should be
658 ;; Tue Jul 9 09:04:21 1996
659
660 ;; Fixme: This should use timezone on the date field contents.
661 (setq date
662 (cond ((not date)
663 "Tue Jan 1 00:00:0 1900")
664 ((string-match "[A-Z]" (nth 0 date))
665 (format "%s %s %s %s %s"
666 (nth 0 date) (nth 2 date) (nth 1 date)
667 (nth 4 date) (nth 3 date)))
668 (t
669 ;; this really needs to be better but I don't feel
670 ;; like writing a date to day converter.
671 (format "Sun %s %s %s %s"
672 (nth 1 date) (nth 0 date)
673 (nth 3 date) (nth 2 date)))
674 ))
675 (setq From_ (format "\nFrom %s %s\n" from date))
676 (while (string-match "," From_)
677 (setq From_ (concat (substring From_ 0 (match-beginning 0))
678 (substring From_ (match-end 0)))))
679 (goto-char (point-min))
680 (insert From_)
681 (if (search-forward "\n\n" nil t)
682 nil
683 (goto-char (point-max))
684 (insert "\n"))
685 (let ((size (- (point-max) (point))))
686 (forward-line -1)
687 (insert (format "Content-Length: %s\n" size)))
688 )))))
689
690;; The Command Set
691
692;; AUTHORIZATION STATE
693
694(defun pop3-user (process user)
695 "Send USER information to POP3 server."
696 (pop3-send-command process (format "USER %s" user))
697 (let ((response (pop3-read-response process t)))
698 (if (not (and response (string-match "+OK" response)))
699 (error "USER %s not valid" user))))
700
701(defun pop3-pass (process)
702 "Send authentication information to the server."
703 (pop3-send-command process (format "PASS %s" pop3-password))
704 (let ((response (pop3-read-response process t)))
705 (if (not (and response (string-match "+OK" response)))
706 (pop3-quit process))))
707
708(defun pop3-apop (process user)
709 "Send alternate authentication information to the server."
710 (let ((pass pop3-password))
711 (if (and pop3-password-required (not pass))
712 (setq pass
713 (read-passwd (format "Password for %s: " pop3-maildrop))))
714 (if pass
715 (let ((hash (md5 (concat pop3-timestamp pass) nil nil 'binary)))
716 (pop3-send-command process (format "APOP %s %s" user hash))
717 (let ((response (pop3-read-response process t)))
718 (if (not (and response (string-match "+OK" response)))
719 (pop3-quit process)))))
720 ))
721
722;; TRANSACTION STATE
723
724(defun pop3-stat (process)
725 "Return the number of messages in the maildrop and the maildrop's size."
726 (pop3-send-command process "STAT")
727 (let ((response (pop3-read-response process t)))
728 (list (string-to-number (nth 1 (split-string response " ")))
729 (string-to-number (nth 2 (split-string response " "))))
730 ))
731
732(defun pop3-list (process &optional msg)
733 "If MSG is nil, return an alist of (MESSAGE-ID . SIZE) pairs.
734Otherwise, return the size of the message-id MSG"
735 (pop3-send-command process (if msg
736 (format "LIST %d" msg)
737 "LIST"))
738 (let ((response (pop3-read-response process t)))
739 (if msg
740 (string-to-number (nth 2 (split-string response " ")))
741 (let ((start pop3-read-point) end)
742 (with-current-buffer (process-buffer process)
743 (while (not (re-search-forward "^\\.\r\n" nil t))
744 (pop3-accept-process-output process)
745 (goto-char start))
746 (setq pop3-read-point (point-marker))
747 (goto-char (match-beginning 0))
748 (setq end (point-marker))
749 (mapcar #'(lambda (s) (let ((split (split-string s " ")))
750 (cons (string-to-number (nth 0 split))
751 (string-to-number (nth 1 split)))))
752 (split-string (buffer-substring start end) "\r\n" t)))))))
753
754(defun pop3-retr (process msg crashbuf)
755 "Retrieve message-id MSG to buffer CRASHBUF."
756 (pop3-send-command process (format "RETR %s" msg))
757 (pop3-read-response process)
758 (let ((start pop3-read-point) end)
759 (with-current-buffer (process-buffer process)
760 (while (not (re-search-forward "^\\.\r\n" nil t))
761 (unless (memq (process-status process) '(open run))
762 (error "pop3 server closed the connection"))
763 (pop3-accept-process-output process)
764 (goto-char start))
765 (setq pop3-read-point (point-marker))
766 ;; this code does not seem to work for some POP servers...
767 ;; and I cannot figure out why not.
768 ;; (goto-char (match-beginning 0))
769 ;; (backward-char 2)
770 ;; (if (not (looking-at "\r\n"))
771 ;; (insert "\r\n"))
772 ;; (re-search-forward "\\.\r\n")
773 (goto-char (match-beginning 0))
774 (setq end (point-marker))
775 (pop3-clean-region start end)
776 (pop3-munge-message-separator start end)
777 (with-current-buffer crashbuf
778 (erase-buffer))
779 (copy-to-buffer crashbuf start end)
780 (delete-region start end)
781 )))
782
783(defun pop3-dele (process msg)
784 "Mark message-id MSG as deleted."
785 (pop3-send-command process (format "DELE %s" msg))
786 (pop3-read-response process))
787
788(defun pop3-noop (process msg)
789 "No-operation."
790 (pop3-send-command process "NOOP")
791 (pop3-read-response process))
792
793(defun pop3-last (process)
794 "Return highest accessed message-id number for the session."
795 (pop3-send-command process "LAST")
796 (let ((response (pop3-read-response process t)))
797 (string-to-number (nth 1 (split-string response " ")))
798 ))
799
800(defun pop3-rset (process)
801 "Remove all delete marks from current maildrop."
802 (pop3-send-command process "RSET")
803 (pop3-read-response process))
804
805;; UPDATE
806
807(defun pop3-quit (process)
808 "Close connection to POP3 server.
809Tell server to remove all messages marked as deleted, unlock the maildrop,
810and close the connection."
811 (pop3-send-command process "QUIT")
812 (pop3-read-response process t)
813 (if process
814 (with-current-buffer (process-buffer process)
815 (goto-char (point-max))
816 (delete-process process))))
817
818;; Summary of POP3 (Post Office Protocol version 3) commands and responses
819
820;;; AUTHORIZATION STATE
821
822;; Initial TCP connection
823;; Arguments: none
824;; Restrictions: none
825;; Possible responses:
826;; +OK [POP3 server ready]
827
828;; USER name
829;; Arguments: a server specific user-id (required)
830;; Restrictions: authorization state [after unsuccessful USER or PASS
831;; Possible responses:
832;; +OK [valid user-id]
833;; -ERR [invalid user-id]
834
835;; PASS string
836;; Arguments: a server/user-id specific password (required)
837;; Restrictions: authorization state, after successful USER
838;; Possible responses:
839;; +OK [maildrop locked and ready]
840;; -ERR [invalid password]
841;; -ERR [unable to lock maildrop]
842
843;; STLS (RFC 2595)
844;; Arguments: none
845;; Restrictions: Only permitted in AUTHORIZATION state.
846;; Possible responses:
847;; +OK
848;; -ERR
849
850;;; TRANSACTION STATE
851
852;; STAT
853;; Arguments: none
854;; Restrictions: transaction state
855;; Possible responses:
856;; +OK nn mm [# of messages, size of maildrop]
857
858;; LIST [msg]
859;; Arguments: a message-id (optional)
860;; Restrictions: transaction state; msg must not be deleted
861;; Possible responses:
862;; +OK [scan listing follows]
863;; -ERR [no such message]
864
865;; RETR msg
866;; Arguments: a message-id (required)
867;; Restrictions: transaction state; msg must not be deleted
868;; Possible responses:
869;; +OK [message contents follow]
870;; -ERR [no such message]
871
872;; DELE msg
873;; Arguments: a message-id (required)
874;; Restrictions: transaction state; msg must not be deleted
875;; Possible responses:
876;; +OK [message deleted]
877;; -ERR [no such message]
878
879;; NOOP
880;; Arguments: none
881;; Restrictions: transaction state
882;; Possible responses:
883;; +OK
884
885;; LAST
886;; Arguments: none
887;; Restrictions: transaction state
888;; Possible responses:
889;; +OK nn [highest numbered message accessed]
890
891;; RSET
892;; Arguments: none
893;; Restrictions: transaction state
894;; Possible responses:
895;; +OK [all delete marks removed]
896
897;; UIDL [msg]
898;; Arguments: a message-id (optional)
899;; Restrictions: transaction state; msg must not be deleted
900;; Possible responses:
901;; +OK [uidl listing follows]
902;; -ERR [no such message]
903
904;;; UPDATE STATE
905
906;; QUIT
907;; Arguments: none
908;; Restrictions: none
909;; Possible responses:
910;; +OK [TCP connection closed]
911
912(provide 'pop3)
913
914;;; pop3.el ends here
diff --git a/lisp/net/sieve-manage.el b/lisp/net/sieve-manage.el
new file mode 100644
index 00000000000..695bbd860de
--- /dev/null
+++ b/lisp/net/sieve-manage.el
@@ -0,0 +1,575 @@
1;;; sieve-manage.el --- Implementation of the managesieve protocol in elisp
2
3;; Copyright (C) 2001-2016 Free Software Foundation, Inc.
4
5;; Author: Simon Josefsson <simon@josefsson.org>
6;; Albert Krewinkel <tarleb@moltkeplatz.de>
7
8;; This file is part of GNU Emacs.
9
10;; GNU Emacs is free software: you can redistribute it and/or modify
11;; it under the terms of the GNU General Public License as published by
12;; the Free Software Foundation, either version 3 of the License, or
13;; (at your option) any later version.
14
15;; GNU Emacs is distributed in the hope that it will be useful,
16;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18;; GNU General Public License for more details.
19
20;; You should have received a copy of the GNU General Public License
21;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
22
23;;; Commentary:
24
25;; This library provides an elisp API for the managesieve network
26;; protocol.
27;;
28;; It uses the SASL library for authentication, which means it
29;; supports DIGEST-MD5, CRAM-MD5, SCRAM-MD5, NTLM, PLAIN and LOGIN
30;; methods. STARTTLS is not well tested, but should be easy to get to
31;; work if someone wants.
32;;
33;; The API should be fairly obvious for anyone familiar with the
34;; managesieve protocol, interface functions include:
35;;
36;; `sieve-manage-open'
37;; open connection to managesieve server, returning a buffer to be
38;; used by all other API functions.
39;;
40;; `sieve-manage-opened'
41;; check if a server is open or not
42;;
43;; `sieve-manage-close'
44;; close a server connection.
45;;
46;; `sieve-manage-listscripts'
47;; `sieve-manage-deletescript'
48;; `sieve-manage-getscript'
49;; performs managesieve protocol actions
50;;
51;; and that's it. Example of a managesieve session in *scratch*:
52;;
53;; (with-current-buffer (sieve-manage-open "mail.example.com")
54;; (sieve-manage-authenticate)
55;; (sieve-manage-listscripts))
56;;
57;; => ((active . "main") "vacation")
58;;
59;; References:
60;;
61;; draft-martin-managesieve-02.txt,
62;; "A Protocol for Remotely Managing Sieve Scripts",
63;; by Tim Martin.
64;;
65;; Release history:
66;;
67;; 2001-10-31 Committed to Oort Gnus.
68;; 2002-07-27 Added DELETESCRIPT. Suggested by Ned Ludd.
69;; 2002-08-03 Use SASL library.
70;; 2013-06-05 Enabled STARTTLS support, fixed bit rot.
71
72;;; Code:
73
74(if (locate-library "password-cache")
75 (require 'password-cache)
76 (require 'password))
77
78(eval-when-compile (require 'cl))
79(require 'sasl)
80(require 'starttls)
81(autoload 'sasl-find-mechanism "sasl")
82(autoload 'auth-source-search "auth-source")
83
84;; User customizable variables:
85
86(defgroup sieve-manage nil
87 "Low-level Managesieve protocol issues."
88 :group 'mail
89 :prefix "sieve-")
90
91(defcustom sieve-manage-log "*sieve-manage-log*"
92 "Name of buffer for managesieve session trace."
93 :type 'string
94 :group 'sieve-manage)
95
96(defcustom sieve-manage-server-eol "\r\n"
97 "The EOL string sent from the server."
98 :type 'string
99 :group 'sieve-manage)
100
101(defcustom sieve-manage-client-eol "\r\n"
102 "The EOL string we send to the server."
103 :type 'string
104 :group 'sieve-manage)
105
106(defcustom sieve-manage-authenticators '(digest-md5
107 cram-md5
108 scram-md5
109 ntlm
110 plain
111 login)
112 "Priority of authenticators to consider when authenticating to server."
113 ;; FIXME Improve this. It's not `set'.
114 ;; It's like (repeat (choice (const ...))), where each choice can
115 ;; only appear once.
116 :type '(repeat symbol)
117 :group 'sieve-manage)
118
119(defcustom sieve-manage-authenticator-alist
120 '((cram-md5 sieve-manage-cram-md5-p sieve-manage-cram-md5-auth)
121 (digest-md5 sieve-manage-digest-md5-p sieve-manage-digest-md5-auth)
122 (scram-md5 sieve-manage-scram-md5-p sieve-manage-scram-md5-auth)
123 (ntlm sieve-manage-ntlm-p sieve-manage-ntlm-auth)
124 (plain sieve-manage-plain-p sieve-manage-plain-auth)
125 (login sieve-manage-login-p sieve-manage-login-auth))
126 "Definition of authenticators.
127
128\(NAME CHECK AUTHENTICATE)
129
130NAME names the authenticator. CHECK is a function returning non-nil if
131the server support the authenticator and AUTHENTICATE is a function
132for doing the actual authentication."
133 :type '(repeat (list (symbol :tag "Name") (function :tag "Check function")
134 (function :tag "Authentication function")))
135 :group 'sieve-manage)
136
137(defcustom sieve-manage-default-port "sieve"
138 "Default port number or service name for managesieve protocol."
139 :type '(choice integer string)
140 :version "24.4"
141 :group 'sieve-manage)
142
143(defcustom sieve-manage-default-stream 'network
144 "Default stream type to use for `sieve-manage'."
145 :version "24.1"
146 :type 'symbol
147 :group 'sieve-manage)
148
149;; Internal variables:
150
151(defconst sieve-manage-local-variables '(sieve-manage-server
152 sieve-manage-port
153 sieve-manage-auth
154 sieve-manage-stream
155 sieve-manage-process
156 sieve-manage-client-eol
157 sieve-manage-server-eol
158 sieve-manage-capability))
159(defconst sieve-manage-coding-system-for-read 'binary)
160(defconst sieve-manage-coding-system-for-write 'binary)
161(defvar sieve-manage-stream nil)
162(defvar sieve-manage-auth nil)
163(defvar sieve-manage-server nil)
164(defvar sieve-manage-port nil)
165(defvar sieve-manage-state 'closed
166 "Managesieve state.
167Valid states are `closed', `initial', `nonauth', and `auth'.")
168(defvar sieve-manage-process nil)
169(defvar sieve-manage-capability nil)
170
171;; Internal utility functions
172(autoload 'mm-enable-multibyte "mm-util")
173
174(defun sieve-manage-make-process-buffer ()
175 (with-current-buffer
176 (generate-new-buffer (format " *sieve %s:%s*"
177 sieve-manage-server
178 sieve-manage-port))
179 (mapc 'make-local-variable sieve-manage-local-variables)
180 (mm-enable-multibyte)
181 (buffer-disable-undo)
182 (current-buffer)))
183
184(defun sieve-manage-erase (&optional p buffer)
185 (let ((buffer (or buffer (current-buffer))))
186 (and sieve-manage-log
187 (with-current-buffer (get-buffer-create sieve-manage-log)
188 (mm-enable-multibyte)
189 (buffer-disable-undo)
190 (goto-char (point-max))
191 (insert-buffer-substring buffer (with-current-buffer buffer
192 (point-min))
193 (or p (with-current-buffer buffer
194 (point-max)))))))
195 (delete-region (point-min) (or p (point-max))))
196
197(defun sieve-manage-open-server (server port &optional stream buffer)
198 "Open network connection to SERVER on PORT.
199Return the buffer associated with the connection."
200 (with-current-buffer buffer
201 (sieve-manage-erase)
202 (setq sieve-manage-state 'initial)
203 (destructuring-bind (proc . props)
204 (open-network-stream
205 "SIEVE" buffer server port
206 :type stream
207 :capability-command "CAPABILITY\r\n"
208 :end-of-command "^\\(OK\\|NO\\).*\n"
209 :success "^OK.*\n"
210 :return-list t
211 :starttls-function
212 (lambda (capabilities)
213 (when (string-match "\\bSTARTTLS\\b" capabilities)
214 "STARTTLS\r\n")))
215 (setq sieve-manage-process proc)
216 (setq sieve-manage-capability
217 (sieve-manage-parse-capability (plist-get props :capabilities)))
218 ;; Ignore new capabilities issues after successful STARTTLS
219 (when (and (memq stream '(nil network starttls))
220 (eq (plist-get props :type) 'tls))
221 (sieve-manage-drop-next-answer))
222 (current-buffer))))
223
224;; Authenticators
225(defun sieve-sasl-auth (buffer mech)
226 "Login to server using the SASL MECH method."
227 (message "sieve: Authenticating using %s..." mech)
228 (with-current-buffer buffer
229 (let* ((auth-info (auth-source-search :host sieve-manage-server
230 :port "sieve"
231 :max 1
232 :create t))
233 (user-name (or (plist-get (nth 0 auth-info) :user) ""))
234 (user-password (or (plist-get (nth 0 auth-info) :secret) ""))
235 (user-password (if (functionp user-password)
236 (funcall user-password)
237 user-password))
238 (client (sasl-make-client (sasl-find-mechanism (list mech))
239 user-name "sieve" sieve-manage-server))
240 (sasl-read-passphrase
241 ;; We *need* to copy the password, because sasl will modify it
242 ;; somehow.
243 `(lambda (prompt) ,(copy-sequence user-password)))
244 (step (sasl-next-step client nil))
245 (tag (sieve-manage-send
246 (concat
247 "AUTHENTICATE \""
248 mech
249 "\""
250 (and (sasl-step-data step)
251 (concat
252 " \""
253 (base64-encode-string
254 (sasl-step-data step)
255 'no-line-break)
256 "\"")))))
257 data rsp)
258 (catch 'done
259 (while t
260 (setq rsp nil)
261 (goto-char (point-min))
262 (while (null (or (progn
263 (setq rsp (sieve-manage-is-string))
264 (if (not (and rsp (looking-at
265 sieve-manage-server-eol)))
266 (setq rsp nil)
267 (goto-char (match-end 0))
268 rsp))
269 (setq rsp (sieve-manage-is-okno))))
270 (accept-process-output sieve-manage-process 1)
271 (goto-char (point-min)))
272 (sieve-manage-erase)
273 (when (sieve-manage-ok-p rsp)
274 (when (and (cadr rsp)
275 (string-match "^SASL \"\\([^\"]+\\)\"" (cadr rsp)))
276 (sasl-step-set-data
277 step (base64-decode-string (match-string 1 (cadr rsp)))))
278 (if (and (setq step (sasl-next-step client step))
279 (setq data (sasl-step-data step)))
280 ;; We got data for server but it's finished
281 (error "Server not ready for SASL data: %s" data)
282 ;; The authentication process is finished.
283 (throw 'done t)))
284 (unless (stringp rsp)
285 (error "Server aborted SASL authentication: %s" (caddr rsp)))
286 (sasl-step-set-data step (base64-decode-string rsp))
287 (setq step (sasl-next-step client step))
288 (sieve-manage-send
289 (if (sasl-step-data step)
290 (concat "\""
291 (base64-encode-string (sasl-step-data step)
292 'no-line-break)
293 "\"")
294 ""))))
295 (message "sieve: Login using %s...done" mech))))
296
297(defun sieve-manage-cram-md5-p (buffer)
298 (sieve-manage-capability "SASL" "CRAM-MD5" buffer))
299
300(defun sieve-manage-cram-md5-auth (buffer)
301 "Login to managesieve server using the CRAM-MD5 SASL method."
302 (sieve-sasl-auth buffer "CRAM-MD5"))
303
304(defun sieve-manage-digest-md5-p (buffer)
305 (sieve-manage-capability "SASL" "DIGEST-MD5" buffer))
306
307(defun sieve-manage-digest-md5-auth (buffer)
308 "Login to managesieve server using the DIGEST-MD5 SASL method."
309 (sieve-sasl-auth buffer "DIGEST-MD5"))
310
311(defun sieve-manage-scram-md5-p (buffer)
312 (sieve-manage-capability "SASL" "SCRAM-MD5" buffer))
313
314(defun sieve-manage-scram-md5-auth (buffer)
315 "Login to managesieve server using the SCRAM-MD5 SASL method."
316 (sieve-sasl-auth buffer "SCRAM-MD5"))
317
318(defun sieve-manage-ntlm-p (buffer)
319 (sieve-manage-capability "SASL" "NTLM" buffer))
320
321(defun sieve-manage-ntlm-auth (buffer)
322 "Login to managesieve server using the NTLM SASL method."
323 (sieve-sasl-auth buffer "NTLM"))
324
325(defun sieve-manage-plain-p (buffer)
326 (sieve-manage-capability "SASL" "PLAIN" buffer))
327
328(defun sieve-manage-plain-auth (buffer)
329 "Login to managesieve server using the PLAIN SASL method."
330 (sieve-sasl-auth buffer "PLAIN"))
331
332(defun sieve-manage-login-p (buffer)
333 (sieve-manage-capability "SASL" "LOGIN" buffer))
334
335(defun sieve-manage-login-auth (buffer)
336 "Login to managesieve server using the LOGIN SASL method."
337 (sieve-sasl-auth buffer "LOGIN"))
338
339;; Managesieve API
340
341(defun sieve-manage-open (server &optional port stream auth buffer)
342 "Open a network connection to a managesieve SERVER (string).
343Optional argument PORT is port number (integer) on remote server.
344Optional argument STREAM is any of `sieve-manage-streams' (a symbol).
345Optional argument AUTH indicates authenticator to use, see
346`sieve-manage-authenticators' for available authenticators.
347If nil, chooses the best stream the server is capable of.
348Optional argument BUFFER is buffer (buffer, or string naming buffer)
349to work in."
350 (setq sieve-manage-port (or port sieve-manage-default-port))
351 (with-current-buffer (or buffer (sieve-manage-make-process-buffer))
352 (setq sieve-manage-server (or server
353 sieve-manage-server)
354 sieve-manage-stream (or stream
355 sieve-manage-stream
356 sieve-manage-default-stream)
357 sieve-manage-auth (or auth
358 sieve-manage-auth))
359 (message "sieve: Connecting to %s..." sieve-manage-server)
360 (sieve-manage-open-server sieve-manage-server
361 sieve-manage-port
362 sieve-manage-stream
363 (current-buffer))
364 (when (sieve-manage-opened (current-buffer))
365 ;; Choose authenticator
366 (when (and (null sieve-manage-auth)
367 (not (eq sieve-manage-state 'auth)))
368 (dolist (auth sieve-manage-authenticators)
369 (when (funcall (nth 1 (assq auth sieve-manage-authenticator-alist))
370 buffer)
371 (setq sieve-manage-auth auth)
372 (return)))
373 (unless sieve-manage-auth
374 (error "Couldn't figure out authenticator for server")))
375 (sieve-manage-erase)
376 (current-buffer))))
377
378(defun sieve-manage-authenticate (&optional buffer)
379 "Authenticate on server in BUFFER.
380Return `sieve-manage-state' value."
381 (with-current-buffer (or buffer (current-buffer))
382 (if (eq sieve-manage-state 'nonauth)
383 (when (funcall (nth 2 (assq sieve-manage-auth
384 sieve-manage-authenticator-alist))
385 (current-buffer))
386 (setq sieve-manage-state 'auth))
387 sieve-manage-state)))
388
389(defun sieve-manage-opened (&optional buffer)
390 "Return non-nil if connection to managesieve server in BUFFER is open.
391If BUFFER is nil then the current buffer is used."
392 (and (setq buffer (get-buffer (or buffer (current-buffer))))
393 (buffer-live-p buffer)
394 (with-current-buffer buffer
395 (and sieve-manage-process
396 (memq (process-status sieve-manage-process) '(open run))))))
397
398(defun sieve-manage-close (&optional buffer)
399 "Close connection to managesieve server in BUFFER.
400If BUFFER is nil, the current buffer is used."
401 (with-current-buffer (or buffer (current-buffer))
402 (when (sieve-manage-opened)
403 (sieve-manage-send "LOGOUT")
404 (sit-for 1))
405 (when (and sieve-manage-process
406 (memq (process-status sieve-manage-process) '(open run)))
407 (delete-process sieve-manage-process))
408 (setq sieve-manage-process nil)
409 (sieve-manage-erase)
410 t))
411
412(defun sieve-manage-capability (&optional name value buffer)
413 "Check if capability NAME of server BUFFER match VALUE.
414If it does, return the server value of NAME. If not returns nil.
415If VALUE is nil, do not check VALUE and return server value.
416If NAME is nil, return the full server list of capabilities."
417 (with-current-buffer (or buffer (current-buffer))
418 (if (null name)
419 sieve-manage-capability
420 (let ((server-value (cadr (assoc name sieve-manage-capability))))
421 (when (or (null value)
422 (and server-value
423 (string-match value server-value)))
424 server-value)))))
425
426(defun sieve-manage-listscripts (&optional buffer)
427 (with-current-buffer (or buffer (current-buffer))
428 (sieve-manage-send "LISTSCRIPTS")
429 (sieve-manage-parse-listscripts)))
430
431(defun sieve-manage-havespace (name size &optional buffer)
432 (with-current-buffer (or buffer (current-buffer))
433 (sieve-manage-send (format "HAVESPACE \"%s\" %s" name size))
434 (sieve-manage-parse-okno)))
435
436(defun sieve-manage-putscript (name content &optional buffer)
437 (with-current-buffer (or buffer (current-buffer))
438 (sieve-manage-send (format "PUTSCRIPT \"%s\" {%d+}%s%s" name
439 ;; Here we assume that the coding-system will
440 ;; replace each char with a single byte.
441 ;; This is always the case if `content' is
442 ;; a unibyte string.
443 (length content)
444 sieve-manage-client-eol content))
445 (sieve-manage-parse-okno)))
446
447(defun sieve-manage-deletescript (name &optional buffer)
448 (with-current-buffer (or buffer (current-buffer))
449 (sieve-manage-send (format "DELETESCRIPT \"%s\"" name))
450 (sieve-manage-parse-okno)))
451
452(defun sieve-manage-getscript (name output-buffer &optional buffer)
453 (with-current-buffer (or buffer (current-buffer))
454 (sieve-manage-send (format "GETSCRIPT \"%s\"" name))
455 (let ((script (sieve-manage-parse-string)))
456 (sieve-manage-parse-crlf)
457 (with-current-buffer output-buffer
458 (insert script))
459 (sieve-manage-parse-okno))))
460
461(defun sieve-manage-setactive (name &optional buffer)
462 (with-current-buffer (or buffer (current-buffer))
463 (sieve-manage-send (format "SETACTIVE \"%s\"" name))
464 (sieve-manage-parse-okno)))
465
466;; Protocol parsing routines
467
468(defun sieve-manage-wait-for-answer ()
469 (let ((pattern "^\\(OK\\|NO\\).*\n")
470 pos)
471 (while (not pos)
472 (setq pos (search-forward-regexp pattern nil t))
473 (goto-char (point-min))
474 (sleep-for 0 50))
475 pos))
476
477(defun sieve-manage-drop-next-answer ()
478 (sieve-manage-wait-for-answer)
479 (sieve-manage-erase))
480
481(defun sieve-manage-ok-p (rsp)
482 (string= (downcase (or (car-safe rsp) "")) "ok"))
483
484(defun sieve-manage-is-okno ()
485 (when (looking-at (concat
486 "^\\(OK\\|NO\\)\\( (\\([^)]+\\))\\)?\\( \\(.*\\)\\)?"
487 sieve-manage-server-eol))
488 (let ((status (match-string 1))
489 (resp-code (match-string 3))
490 (response (match-string 5)))
491 (when response
492 (goto-char (match-beginning 5))
493 (setq response (sieve-manage-is-string)))
494 (list status resp-code response))))
495
496(defun sieve-manage-parse-okno ()
497 (let (rsp)
498 (while (null rsp)
499 (accept-process-output (get-buffer-process (current-buffer)) 1)
500 (goto-char (point-min))
501 (setq rsp (sieve-manage-is-okno)))
502 (sieve-manage-erase)
503 rsp))
504
505(defun sieve-manage-parse-capability (str)
506 "Parse managesieve capability string `STR'.
507Set variable `sieve-manage-capability' to "
508 (let ((capas (delq nil
509 (mapcar #'split-string-and-unquote
510 (split-string str "\n")))))
511 (when (string= "OK" (caar (last capas)))
512 (setq sieve-manage-state 'nonauth))
513 capas))
514
515(defun sieve-manage-is-string ()
516 (cond ((looking-at "\"\\([^\"]+\\)\"")
517 (prog1
518 (match-string 1)
519 (goto-char (match-end 0))))
520 ((looking-at (concat "{\\([0-9]+\\+?\\)}" sieve-manage-server-eol))
521 (let ((pos (match-end 0))
522 (len (string-to-number (match-string 1))))
523 (if (< (point-max) (+ pos len))
524 nil
525 (goto-char (+ pos len))
526 (buffer-substring pos (+ pos len)))))))
527
528(defun sieve-manage-parse-string ()
529 (let (rsp)
530 (while (null rsp)
531 (accept-process-output (get-buffer-process (current-buffer)) 1)
532 (goto-char (point-min))
533 (setq rsp (sieve-manage-is-string)))
534 (sieve-manage-erase (point))
535 rsp))
536
537(defun sieve-manage-parse-crlf ()
538 (when (looking-at sieve-manage-server-eol)
539 (sieve-manage-erase (match-end 0))))
540
541(defun sieve-manage-parse-listscripts ()
542 (let (tmp rsp data)
543 (while (null rsp)
544 (while (null (or (setq rsp (sieve-manage-is-okno))
545 (setq tmp (sieve-manage-is-string))))
546 (accept-process-output (get-buffer-process (current-buffer)) 1)
547 (goto-char (point-min)))
548 (when tmp
549 (while (not (looking-at (concat "\\( ACTIVE\\)?"
550 sieve-manage-server-eol)))
551 (accept-process-output (get-buffer-process (current-buffer)) 1)
552 (goto-char (point-min)))
553 (if (match-string 1)
554 (push (cons 'active tmp) data)
555 (push tmp data))
556 (goto-char (match-end 0))
557 (setq tmp nil)))
558 (sieve-manage-erase)
559 (if (sieve-manage-ok-p rsp)
560 data
561 rsp)))
562
563(defun sieve-manage-send (cmdstr)
564 (setq cmdstr (concat cmdstr sieve-manage-client-eol))
565 (and sieve-manage-log
566 (with-current-buffer (get-buffer-create sieve-manage-log)
567 (mm-enable-multibyte)
568 (buffer-disable-undo)
569 (goto-char (point-max))
570 (insert cmdstr)))
571 (process-send-string sieve-manage-process cmdstr))
572
573(provide 'sieve-manage)
574
575;; sieve-manage.el ends here
diff --git a/lisp/net/sieve-mode.el b/lisp/net/sieve-mode.el
new file mode 100644
index 00000000000..7575ba67c5e
--- /dev/null
+++ b/lisp/net/sieve-mode.el
@@ -0,0 +1,221 @@
1;;; sieve-mode.el --- Sieve code editing commands for Emacs
2
3;; Copyright (C) 2001-2016 Free Software Foundation, Inc.
4
5;; Author: Simon Josefsson <simon@josefsson.org>
6
7;; This file is part of GNU Emacs.
8
9;; GNU Emacs is free software: you can redistribute it and/or modify
10;; it under the terms of the GNU General Public License as published by
11;; the Free Software Foundation, either version 3 of the License, or
12;; (at your option) any later version.
13
14;; GNU Emacs is distributed in the hope that it will be useful,
15;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17;; GNU General Public License for more details.
18
19;; You should have received a copy of the GNU General Public License
20;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
21
22;;; Commentary:
23
24;; This file contain editing mode functions and font-lock support for
25;; editing Sieve scripts. It sets up C-mode with support for
26;; sieve-style #-comments and a lightly hacked syntax table. It was
27;; strongly influenced by awk-mode.el.
28;;
29;; Put something similar to the following in your .emacs to use this file:
30;;
31;; (load "~/lisp/sieve")
32;; (setq auto-mode-alist (cons '("\\.siv\\'" . sieve-mode) auto-mode-alist))
33;;
34;; References:
35;;
36;; RFC 3028,
37;; "Sieve: A Mail Filtering Language",
38;; by Tim Showalter.
39;;
40;; Release history:
41;;
42;; 2001-03-02 version 1.0 posted to gnu.emacs.sources
43;; version 1.1 change file extension into ".siv" (official one)
44;; added keymap and menubar to hook into sieve-manage
45;; 2001-10-31 version 1.2 committed to Oort Gnus
46
47;;; Code:
48
49(autoload 'sieve-manage "sieve")
50(autoload 'sieve-upload "sieve")
51(eval-when-compile
52 (require 'font-lock))
53
54(defgroup sieve nil
55 "Sieve."
56 :group 'languages)
57
58(defcustom sieve-mode-hook nil
59 "Hook run in sieve mode buffers."
60 :group 'sieve
61 :type 'hook)
62
63;; Font-lock
64
65(defvar sieve-control-commands-face 'sieve-control-commands
66 "Face name used for Sieve Control Commands.")
67
68(defface sieve-control-commands
69 '((((type tty) (class color)) (:foreground "blue" :weight light))
70 (((class grayscale) (background light)) (:foreground "LightGray" :bold t))
71 (((class grayscale) (background dark)) (:foreground "DimGray" :bold t))
72 (((class color) (background light)) (:foreground "Orchid"))
73 (((class color) (background dark)) (:foreground "LightSteelBlue"))
74 (t (:bold t)))
75 "Face used for Sieve Control Commands."
76 :group 'sieve)
77;; backward-compatibility alias
78(put 'sieve-control-commands-face 'face-alias 'sieve-control-commands)
79(put 'sieve-control-commands-face 'obsolete-face "22.1")
80
81(defvar sieve-action-commands-face 'sieve-action-commands
82 "Face name used for Sieve Action Commands.")
83
84(defface sieve-action-commands
85 '((((type tty) (class color)) (:foreground "blue" :weight bold))
86 (((class color) (background light)) (:foreground "Blue"))
87 (((class color) (background dark)) (:foreground "LightSkyBlue"))
88 (t (:inverse-video t :bold t)))
89 "Face used for Sieve Action Commands."
90 :group 'sieve)
91;; backward-compatibility alias
92(put 'sieve-action-commands-face 'face-alias 'sieve-action-commands)
93(put 'sieve-action-commands-face 'obsolete-face "22.1")
94
95(defvar sieve-test-commands-face 'sieve-test-commands
96 "Face name used for Sieve Test Commands.")
97
98(defface sieve-test-commands
99 '((((type tty) (class color)) (:foreground "magenta"))
100 (((class grayscale) (background light))
101 (:foreground "LightGray" :bold t :underline t))
102 (((class grayscale) (background dark))
103 (:foreground "Gray50" :bold t :underline t))
104 (((class color) (background light)) (:foreground "CadetBlue"))
105 (((class color) (background dark)) (:foreground "Aquamarine"))
106 (t (:bold t :underline t)))
107 "Face used for Sieve Test Commands."
108 :group 'sieve)
109;; backward-compatibility alias
110(put 'sieve-test-commands-face 'face-alias 'sieve-test-commands)
111(put 'sieve-test-commands-face 'obsolete-face "22.1")
112
113(defvar sieve-tagged-arguments-face 'sieve-tagged-arguments
114 "Face name used for Sieve Tagged Arguments.")
115
116(defface sieve-tagged-arguments
117 '((((type tty) (class color)) (:foreground "cyan" :weight bold))
118 (((class grayscale) (background light)) (:foreground "LightGray" :bold t))
119 (((class grayscale) (background dark)) (:foreground "DimGray" :bold t))
120 (((class color) (background light)) (:foreground "Purple"))
121 (((class color) (background dark)) (:foreground "Cyan"))
122 (t (:bold t)))
123 "Face used for Sieve Tagged Arguments."
124 :group 'sieve)
125;; backward-compatibility alias
126(put 'sieve-tagged-arguments-face 'face-alias 'sieve-tagged-arguments)
127(put 'sieve-tagged-arguments-face 'obsolete-face "22.1")
128
129
130(defconst sieve-font-lock-keywords
131 (eval-when-compile
132 (list
133 ;; control commands
134 (cons (regexp-opt '("require" "if" "else" "elsif" "stop")
135 'words)
136 'sieve-control-commands-face)
137 ;; action commands
138 (cons (regexp-opt '("fileinto" "redirect" "reject" "keep" "discard")
139 'words)
140 'sieve-action-commands-face)
141 ;; test commands
142 (cons (regexp-opt '("address" "allof" "anyof" "exists" "false"
143 "true" "header" "not" "size" "envelope"
144 "body")
145 'words)
146 'sieve-test-commands-face)
147 (cons "\\Sw+:\\sw+"
148 'sieve-tagged-arguments-face))))
149
150;; Syntax table
151
152(defvar sieve-mode-syntax-table nil
153 "Syntax table in use in sieve-mode buffers.")
154
155(if sieve-mode-syntax-table
156 ()
157 (setq sieve-mode-syntax-table (make-syntax-table))
158 (modify-syntax-entry ?\\ "\\" sieve-mode-syntax-table)
159 (modify-syntax-entry ?\n "> " sieve-mode-syntax-table)
160 (modify-syntax-entry ?\f "> " sieve-mode-syntax-table)
161 (modify-syntax-entry ?\# "< " sieve-mode-syntax-table)
162 (modify-syntax-entry ?/ "." sieve-mode-syntax-table)
163 (modify-syntax-entry ?* "." sieve-mode-syntax-table)
164 (modify-syntax-entry ?+ "." sieve-mode-syntax-table)
165 (modify-syntax-entry ?- "." sieve-mode-syntax-table)
166 (modify-syntax-entry ?= "." sieve-mode-syntax-table)
167 (modify-syntax-entry ?% "." sieve-mode-syntax-table)
168 (modify-syntax-entry ?< "." sieve-mode-syntax-table)
169 (modify-syntax-entry ?> "." sieve-mode-syntax-table)
170 (modify-syntax-entry ?& "." sieve-mode-syntax-table)
171 (modify-syntax-entry ?| "." sieve-mode-syntax-table)
172 (modify-syntax-entry ?_ "_" sieve-mode-syntax-table)
173 (modify-syntax-entry ?\' "\"" sieve-mode-syntax-table))
174
175;; Key map definition
176
177(defvar sieve-mode-map
178 (let ((map (make-sparse-keymap)))
179 (define-key map "\C-c\C-l" 'sieve-upload)
180 (define-key map "\C-c\C-c" 'sieve-upload-and-kill)
181 (define-key map "\C-c\C-m" 'sieve-manage)
182 map)
183 "Key map used in sieve mode.")
184
185;; Menu definition
186
187(defvar sieve-mode-menu nil
188 "Menubar used in sieve mode.")
189
190;; Code for Sieve editing mode.
191(autoload 'easy-menu-add-item "easymenu")
192
193;;;###autoload
194(define-derived-mode sieve-mode c-mode "Sieve"
195 "Major mode for editing Sieve code.
196This is much like C mode except for the syntax of comments. Its keymap
197inherits from C mode's and it has the same variables for customizing
198indentation. It has its own abbrev table and its own syntax table.
199
200Turning on Sieve mode runs `sieve-mode-hook'."
201 (set (make-local-variable 'paragraph-start) (concat "$\\|" page-delimiter))
202 (set (make-local-variable 'paragraph-separate) paragraph-start)
203 (set (make-local-variable 'comment-start) "#")
204 (set (make-local-variable 'comment-end) "")
205 ;;(set (make-local-variable 'comment-start-skip) "\\(^\\|\\s-\\);?#+ *")
206 (set (make-local-variable 'comment-start-skip) "#+ *")
207 (set (make-local-variable 'font-lock-defaults)
208 '(sieve-font-lock-keywords nil nil ((?_ . "w"))))
209 (easy-menu-add-item nil nil sieve-mode-menu))
210
211;; Menu
212
213(easy-menu-define sieve-mode-menu sieve-mode-map
214 "Sieve Menu."
215 '("Sieve"
216 ["Upload script" sieve-upload t]
217 ["Manage scripts on server" sieve-manage t]))
218
219(provide 'sieve-mode)
220
221;; sieve-mode.el ends here
diff --git a/lisp/net/sieve.el b/lisp/net/sieve.el
new file mode 100644
index 00000000000..2046e53697d
--- /dev/null
+++ b/lisp/net/sieve.el
@@ -0,0 +1,372 @@
1;;; sieve.el --- Utilities to manage sieve scripts
2
3;; Copyright (C) 2001-2016 Free Software Foundation, Inc.
4
5;; Author: Simon Josefsson <simon@josefsson.org>
6
7;; This file is part of GNU Emacs.
8
9;; GNU Emacs is free software: you can redistribute it and/or modify
10;; it under the terms of the GNU General Public License as published by
11;; the Free Software Foundation, either version 3 of the License, or
12;; (at your option) any later version.
13
14;; GNU Emacs is distributed in the hope that it will be useful,
15;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17;; GNU General Public License for more details.
18
19;; You should have received a copy of the GNU General Public License
20;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
21
22;;; Commentary:
23
24;; This file contain utilities to facilitate upload, download and
25;; general management of sieve scripts. Currently only the
26;; Managesieve protocol is supported (using sieve-manage.el), but when
27;; (useful) alternatives become available, they might be supported as
28;; well.
29;;
30;; The cursor navigation was inspired by biff-mode by Franklin Lee.
31;;
32;; Release history:
33;;
34;; 2001-10-31 Committed to Oort Gnus.
35;; 2002-07-27 Fix down-mouse-2 and down-mouse-3 in manage-mode. Fix menubar
36;; in manage-mode. Change some messages. Added sieve-deactivate*,
37;; sieve-remove. Fixed help text in manage-mode. Suggested by
38;; Ned Ludd.
39;;
40;; Todo:
41;;
42;; * Namespace? This file contains `sieve-manage' and
43;; `sieve-manage-mode', but there is a sieve-manage.el file as well.
44;; Can't think of a good solution though, this file need a *-mode,
45;; and naming it `sieve-mode' would collide with sieve-mode.el. One
46;; solution would be to come up with some better name that this file
47;; can use that doesn't have the managesieve specific "manage" in
48;; it. sieve-dired? i dunno. we could copy all off sieve.el into
49;; sieve-manage.el too, but I'd like to separate the interface from
50;; the protocol implementation since the backends are likely to
51;; change (well).
52;;
53;; * Define servers? We could have a customize buffer to create a server,
54;; with authentication/stream/etc parameters, much like Gnus, and then
55;; only use names of defined servers when interacting with M-x sieve-*.
56;; Right now you can't use STARTTLS, which sieve-manage.el provides
57
58;;; Code:
59
60(require 'sieve-manage)
61(require 'sieve-mode)
62
63;; User customizable variables:
64
65(defgroup sieve nil
66 "Manage sieve scripts."
67 :version "22.1"
68 :group 'tools)
69
70(defcustom sieve-new-script "<new script>"
71 "Name of name script indicator."
72 :type 'string
73 :group 'sieve)
74
75(defcustom sieve-buffer "*sieve*"
76 "Name of sieve management buffer."
77 :type 'string
78 :group 'sieve)
79
80(defcustom sieve-template "\
81require \"fileinto\";
82
83# Example script (remove comment character '#' to make it effective!):
84#
85# if header :contains \"from\" \"coyote\" {
86# discard;
87# } elsif header :contains [\"subject\"] [\"$$$\"] {
88# discard;
89# } else {
90# fileinto \"INBOX\";
91# }
92"
93 "Template sieve script."
94 :type 'string
95 :group 'sieve)
96
97;; Internal variables:
98
99(defvar sieve-manage-buffer nil)
100(defvar sieve-buffer-header-end nil)
101(defvar sieve-buffer-script-name nil
102 "The real script name of the buffer.")
103(make-local-variable 'sieve-buffer-script-name)
104
105;; Sieve-manage mode:
106
107(defvar sieve-manage-mode-map
108 (let ((map (make-sparse-keymap)))
109 ;; various
110 (define-key map "?" 'sieve-help)
111 (define-key map "h" 'sieve-help)
112 ;; activating
113 (define-key map "m" 'sieve-activate)
114 (define-key map "u" 'sieve-deactivate)
115 (define-key map "\M-\C-?" 'sieve-deactivate-all)
116 ;; navigation keys
117 (define-key map "\C-p" 'sieve-prev-line)
118 (define-key map [up] 'sieve-prev-line)
119 (define-key map "\C-n" 'sieve-next-line)
120 (define-key map [down] 'sieve-next-line)
121 (define-key map " " 'sieve-next-line)
122 (define-key map "n" 'sieve-next-line)
123 (define-key map "p" 'sieve-prev-line)
124 (define-key map "\C-m" 'sieve-edit-script)
125 (define-key map "f" 'sieve-edit-script)
126 (define-key map "o" 'sieve-edit-script-other-window)
127 (define-key map "r" 'sieve-remove)
128 (define-key map "q" 'sieve-bury-buffer)
129 (define-key map "Q" 'sieve-manage-quit)
130 (define-key map [(down-mouse-2)] 'sieve-edit-script)
131 (define-key map [(down-mouse-3)] 'sieve-manage-mode-menu)
132 map)
133 "Keymap for `sieve-manage-mode'.")
134
135(easy-menu-define sieve-manage-mode-menu sieve-manage-mode-map
136 "Sieve Menu."
137 '("Manage Sieve"
138 ["Edit script" sieve-edit-script t]
139 ["Activate script" sieve-activate t]
140 ["Deactivate script" sieve-deactivate t]))
141
142(define-derived-mode sieve-manage-mode fundamental-mode "Sieve-manage"
143 "Mode used for sieve script management."
144 (buffer-disable-undo (current-buffer))
145 (setq truncate-lines t)
146 (easy-menu-add sieve-manage-mode-menu sieve-manage-mode-map))
147
148(put 'sieve-manage-mode 'mode-class 'special)
149
150;; Commands used in sieve-manage mode:
151
152(defun sieve-manage-quit ()
153 "Quit Manage Sieve and close the connection."
154 (interactive)
155 (sieve-manage-close sieve-manage-buffer)
156 (kill-buffer sieve-manage-buffer)
157 (kill-buffer (current-buffer)))
158
159(defun sieve-bury-buffer ()
160 "Bury the Manage Sieve buffer without closing the connection."
161 (interactive)
162 (bury-buffer))
163
164(defun sieve-activate (&optional pos)
165 (interactive "d")
166 (let ((name (sieve-script-at-point)) err)
167 (when (or (null name) (string-equal name sieve-new-script))
168 (error "No sieve script at point"))
169 (message "Activating script %s..." name)
170 (setq err (sieve-manage-setactive name sieve-manage-buffer))
171 (sieve-refresh-scriptlist)
172 (if (sieve-manage-ok-p err)
173 (message "Activating script %s...done" name)
174 (message "Activating script %s...failed: %s" name (nth 2 err)))))
175
176(defun sieve-deactivate-all (&optional pos)
177 (interactive "d")
178 (let ((name (sieve-script-at-point)) err)
179 (message "Deactivating scripts...")
180 (setq err (sieve-manage-setactive "" sieve-manage-buffer))
181 (sieve-refresh-scriptlist)
182 (if (sieve-manage-ok-p err)
183 (message "Deactivating scripts...done")
184 (message "Deactivating scripts...failed: %s" (nth 2 err)))))
185
186(defalias 'sieve-deactivate 'sieve-deactivate-all)
187
188(defun sieve-remove (&optional pos)
189 (interactive "d")
190 (let ((name (sieve-script-at-point)) err)
191 (when (or (null name) (string-equal name sieve-new-script))
192 (error "No sieve script at point"))
193 (message "Removing sieve script %s..." name)
194 (setq err (sieve-manage-deletescript name sieve-manage-buffer))
195 (unless (sieve-manage-ok-p err)
196 (error "Removing sieve script %s...failed: " err))
197 (sieve-refresh-scriptlist)
198 (message "Removing sieve script %s...done" name)))
199
200(defun sieve-edit-script (&optional pos)
201 (interactive "d")
202 (let ((name (sieve-script-at-point)))
203 (unless name
204 (error "No sieve script at point"))
205 (if (not (string-equal name sieve-new-script))
206 (let ((newbuf (generate-new-buffer name))
207 err)
208 (setq err (sieve-manage-getscript name newbuf sieve-manage-buffer))
209 (switch-to-buffer newbuf)
210 (unless (sieve-manage-ok-p err)
211 (error "Sieve download failed: %s" err)))
212 (switch-to-buffer (get-buffer-create "template.siv"))
213 (insert sieve-template))
214 (sieve-mode)
215 (setq sieve-buffer-script-name name)
216 (goto-char (point-min))
217 (message
218 (substitute-command-keys
219 "Press \\[sieve-upload] to upload script to server."))))
220
221(defmacro sieve-change-region (&rest body)
222 "Turns off sieve-region before executing BODY, then re-enables it after.
223Used to bracket operations which move point in the sieve-buffer."
224 `(progn
225 (sieve-highlight nil)
226 ,@body
227 (sieve-highlight t)))
228(put 'sieve-change-region 'lisp-indent-function 0)
229
230(defun sieve-next-line (&optional arg)
231 (interactive)
232 (unless arg
233 (setq arg 1))
234 (if (save-excursion
235 (forward-line arg)
236 (sieve-script-at-point))
237 (sieve-change-region
238 (forward-line arg))
239 (message "End of list")))
240
241(defun sieve-prev-line (&optional arg)
242 (interactive)
243 (unless arg
244 (setq arg -1))
245 (if (save-excursion
246 (forward-line arg)
247 (sieve-script-at-point))
248 (sieve-change-region
249 (forward-line arg))
250 (message "Beginning of list")))
251
252(defun sieve-help ()
253 "Display help for various sieve commands."
254 (interactive)
255 (if (eq last-command 'sieve-help)
256 ;; would need minor-mode for log-edit-mode
257 (describe-function 'sieve-mode)
258 (message "%s" (substitute-command-keys
259 "`\\[sieve-edit-script]':edit `\\[sieve-activate]':activate `\\[sieve-deactivate]':deactivate `\\[sieve-remove]':remove"))))
260
261;; Create buffer:
262
263(defun sieve-setup-buffer (server port)
264 (setq buffer-read-only nil)
265 (erase-buffer)
266 (buffer-disable-undo)
267 (let* ((port (or port sieve-manage-default-port))
268 (header (format "Server : %s:%s\n\n" server port)))
269 (insert header))
270 (set (make-local-variable 'sieve-buffer-header-end)
271 (point-max)))
272
273(defun sieve-script-at-point (&optional pos)
274 "Return name of sieve script at point POS, or nil."
275 (interactive "d")
276 (get-char-property (or pos (point)) 'script-name))
277
278(defun sieve-highlight (on)
279 "Turn ON or off highlighting on the current language overlay."
280 (overlay-put (car (overlays-at (point))) 'face (if on 'highlight 'default)))
281
282(defun sieve-insert-scripts (scripts)
283 "Format and insert LANGUAGE-LIST strings into current buffer at point."
284 (while scripts
285 (let ((p (point))
286 (ext nil)
287 (script (pop scripts)))
288 (if (consp script)
289 (insert (format " ACTIVE %s" (cdr script)))
290 (insert (format " %s" script)))
291 (setq ext (make-overlay p (point)))
292 (overlay-put ext 'mouse-face 'highlight)
293 (overlay-put ext 'script-name (if (consp script)
294 (cdr script)
295 script))
296 (insert "\n"))))
297
298(defun sieve-open-server (server &optional port)
299 "Open SERVER (on PORT) and authenticate."
300 (with-current-buffer
301 (or ;; open server
302 (set (make-local-variable 'sieve-manage-buffer)
303 (sieve-manage-open server port))
304 (error "Error opening server %s" server))
305 (sieve-manage-authenticate)))
306
307(defun sieve-refresh-scriptlist ()
308 (interactive)
309 (with-current-buffer sieve-buffer
310 (setq buffer-read-only nil)
311 (delete-region (or sieve-buffer-header-end (point-max)) (point-max))
312 (goto-char (point-max))
313 ;; get list of script names and print them
314 (let ((scripts (sieve-manage-listscripts sieve-manage-buffer)))
315 (if (null scripts)
316 (insert
317 (substitute-command-keys
318 (format
319 "No scripts on server, press \\[sieve-edit-script] on %s to create a new script.\n"
320 sieve-new-script)))
321 (insert
322 (substitute-command-keys
323 (format (concat "%d script%s on server, press \\[sieve-edit-script] on a script "
324 "name edits it, or\npress \\[sieve-edit-script] on %s to create "
325 "a new script.\n") (length scripts)
326 (if (eq (length scripts) 1) "" "s")
327 sieve-new-script))))
328 (save-excursion
329 (sieve-insert-scripts (list sieve-new-script))
330 (sieve-insert-scripts scripts)))
331 (sieve-highlight t)
332 (setq buffer-read-only t)))
333
334;;;###autoload
335(defun sieve-manage (server &optional port)
336 (interactive "sServer: ")
337 (switch-to-buffer (get-buffer-create sieve-buffer))
338 (sieve-manage-mode)
339 (sieve-setup-buffer server port)
340 (if (sieve-open-server server port)
341 (sieve-refresh-scriptlist)
342 (message "Could not open server %s" server)))
343
344;;;###autoload
345(defun sieve-upload (&optional name)
346 (interactive)
347 (when (or (get-buffer sieve-buffer) (call-interactively 'sieve-manage))
348 (let ((script (buffer-string)) err)
349 (with-current-buffer (get-buffer sieve-buffer)
350 (setq err (sieve-manage-putscript
351 (or name sieve-buffer-script-name (buffer-name))
352 script sieve-manage-buffer))
353 (if (sieve-manage-ok-p err)
354 (message (substitute-command-keys
355 "Sieve upload done. Use \\[sieve-manage] to manage scripts."))
356 (message "Sieve upload failed: %s" (nth 2 err)))))))
357
358;;;###autoload
359(defun sieve-upload-and-bury (&optional name)
360 (interactive)
361 (sieve-upload name)
362 (bury-buffer))
363
364;;;###autoload
365(defun sieve-upload-and-kill (&optional name)
366 (interactive)
367 (sieve-upload name)
368 (kill-buffer))
369
370(provide 'sieve)
371
372;; sieve.el ends here
diff --git a/lisp/net/starttls.el b/lisp/net/starttls.el
new file mode 100644
index 00000000000..096ed2adc0d
--- /dev/null
+++ b/lisp/net/starttls.el
@@ -0,0 +1,304 @@
1;;; starttls.el --- STARTTLS functions
2
3;; Copyright (C) 1999-2016 Free Software Foundation, Inc.
4
5;; Author: Daiki Ueno <ueno@unixuser.org>
6;; Author: Simon Josefsson <simon@josefsson.org>
7;; Created: 1999/11/20
8;; Keywords: TLS, SSL, OpenSSL, GnuTLS, mail, news
9
10;; This file is part of GNU Emacs.
11
12;; GNU Emacs is free software: you can redistribute it and/or modify
13;; it under the terms of the GNU General Public License as published by
14;; the Free Software Foundation, either version 3 of the License, or
15;; (at your option) any later version.
16
17;; GNU Emacs is distributed in the hope that it will be useful,
18;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20;; GNU General Public License for more details.
21
22;; You should have received a copy of the GNU General Public License
23;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
24
25;;; Commentary:
26
27;; This module defines some utility functions for STARTTLS profiles.
28
29;; [RFC 2595] "Using TLS with IMAP, POP3 and ACAP"
30;; by Chris Newman <chris.newman@innosoft.com> (1999/06)
31
32;; This file now contains a combination of the two previous
33;; implementations both called "starttls.el". The first one is Daiki
34;; Ueno's starttls.el which uses his own "starttls" command line tool,
35;; and the second one is Simon Josefsson's starttls.el which uses
36;; "gnutls-cli" from GnuTLS.
37;;
38;; If "starttls" is available, it is preferred by the code over
39;; "gnutls-cli", for backwards compatibility. Use
40;; `starttls-use-gnutls' to toggle between implementations if you have
41;; both tools installed. It is recommended to use GnuTLS, though, as
42;; it performs more verification of the certificates.
43
44;; The GnuTLS support requires GnuTLS 0.9.90 (released 2003-10-08) or
45;; later, from <http://www.gnu.org/software/gnutls/>, or "starttls"
46;; from <ftp://ftp.opaopa.org/pub/elisp/>.
47
48;; Usage is similar to `open-network-stream'. For example:
49;;
50;; (when (setq tmp (starttls-open-stream
51;; "test" (current-buffer) "yxa.extundo.com" 25))
52;; (accept-process-output tmp 15)
53;; (process-send-string tmp "STARTTLS\n")
54;; (accept-process-output tmp 15)
55;; (message "STARTTLS output:\n%s" (starttls-negotiate tmp))
56;; (process-send-string tmp "EHLO foo\n"))
57
58;; An example run yields the following output:
59;;
60;; 220 yxa.extundo.com ESMTP Sendmail 8.12.11/8.12.11/Debian-3; Wed, 26 May 2004 19:12:29 +0200; (No UCE/UBE) logging access from: c494102a.s-bi.bostream.se(OK)-c494102a.s-bi.bostream.se [217.215.27.65]
61;; 220 2.0.0 Ready to start TLS
62;; 250-yxa.extundo.com Hello c494102a.s-bi.bostream.se [217.215.27.65], pleased to meet you
63;; 250-ENHANCEDSTATUSCODES
64;; 250-PIPELINING
65;; 250-EXPN
66;; 250-VERB
67;; 250-8BITMIME
68;; 250-SIZE
69;; 250-DSN
70;; 250-ETRN
71;; 250-AUTH DIGEST-MD5 CRAM-MD5 PLAIN LOGIN
72;; 250-DELIVERBY
73;; 250 HELP
74;; nil
75;;
76;; With the message buffer containing:
77;;
78;; STARTTLS output:
79;; *** Starting TLS handshake
80;; - Server's trusted authorities:
81;; [0]: C=SE,ST=Stockholm,L=Stockholm,O=YXA,OU=CA,CN=yxa.extundo.com,EMAIL=staff@yxa.extundo.com
82;; - Certificate type: X.509
83;; - Got a certificate list of 2 certificates.
84;;
85;; - Certificate[0] info:
86;; # The hostname in the certificate matches 'yxa.extundo.com'.
87;; # valid since: Wed May 26 12:16:00 CEST 2004
88;; # expires at: Wed Jul 26 12:16:00 CEST 2023
89;; # serial number: 04
90;; # fingerprint: 7c 04 4b c1 fa 26 9b 5d 90 22 52 3c 65 3d 85 3a
91;; # version: #1
92;; # public key algorithm: RSA
93;; # Modulus: 1024 bits
94;; # Subject's DN: C=SE,ST=Stockholm,L=Stockholm,O=YXA,OU=Mail server,CN=yxa.extundo.com,EMAIL=staff@yxa.extundo.com
95;; # Issuer's DN: C=SE,ST=Stockholm,L=Stockholm,O=YXA,OU=CA,CN=yxa.extundo.com,EMAIL=staff@yxa.extundo.com
96;;
97;; - Certificate[1] info:
98;; # valid since: Sun May 23 11:35:00 CEST 2004
99;; # expires at: Sun Jul 23 11:35:00 CEST 2023
100;; # serial number: 00
101;; # fingerprint: fc 76 d8 63 1a c9 0b 3b fa 40 fe ed 47 7a 58 ae
102;; # version: #3
103;; # public key algorithm: RSA
104;; # Modulus: 1024 bits
105;; # Subject's DN: C=SE,ST=Stockholm,L=Stockholm,O=YXA,OU=CA,CN=yxa.extundo.com,EMAIL=staff@yxa.extundo.com
106;; # Issuer's DN: C=SE,ST=Stockholm,L=Stockholm,O=YXA,OU=CA,CN=yxa.extundo.com,EMAIL=staff@yxa.extundo.com
107;;
108;; - Peer's certificate issuer is unknown
109;; - Peer's certificate is NOT trusted
110;; - Version: TLS 1.0
111;; - Key Exchange: RSA
112;; - Cipher: ARCFOUR 128
113;; - MAC: SHA
114;; - Compression: NULL
115
116;;; Code:
117
118(defgroup starttls nil
119 "Support for `Transport Layer Security' protocol."
120 :version "21.1"
121 :group 'mail)
122
123(defcustom starttls-gnutls-program "gnutls-cli"
124 "Name of GnuTLS command line tool.
125This program is used when GnuTLS is used, i.e. when
126`starttls-use-gnutls' is non-nil."
127 :version "22.1"
128 :type 'string
129 :group 'starttls)
130
131(defcustom starttls-program "starttls"
132 "The program to run in a subprocess to open an TLSv1 connection.
133This program is used when the `starttls' command is used,
134i.e. when `starttls-use-gnutls' is nil."
135 :type 'string
136 :group 'starttls)
137
138(defcustom starttls-use-gnutls (not (executable-find starttls-program))
139 "*Whether to use GnuTLS instead of the `starttls' command."
140 :version "22.1"
141 :type 'boolean
142 :group 'starttls)
143
144(defcustom starttls-extra-args nil
145 "Extra arguments to `starttls-program'.
146These apply when the `starttls' command is used, i.e. when
147`starttls-use-gnutls' is nil."
148 :type '(repeat string)
149 :group 'starttls)
150
151(defcustom starttls-extra-arguments nil
152 "Extra arguments to `starttls-gnutls-program'.
153These apply when GnuTLS is used, i.e. when `starttls-use-gnutls' is non-nil.
154
155For example, non-TLS compliant servers may require
156'(\"--protocols\" \"ssl3\"). Invoke \"gnutls-cli --help\" to
157find out which parameters are available."
158 :version "22.1"
159 :type '(repeat string)
160 :group 'starttls)
161
162(defcustom starttls-process-connection-type nil
163 "*Value for `process-connection-type' to use when starting STARTTLS process."
164 :version "22.1"
165 :type 'boolean
166 :group 'starttls)
167
168(defcustom starttls-connect "- Simple Client Mode:\n\n"
169 "*Regular expression indicating successful connection.
170The default is what GnuTLS's \"gnutls-cli\" outputs."
171 ;; GnuTLS cli.c:main() prints this string when it is starting to run
172 ;; in the application read/write phase. If the logic, or the string
173 ;; itself, is modified, this must be updated.
174 :version "22.1"
175 :type 'regexp
176 :group 'starttls)
177
178(defcustom starttls-failure "\\*\\*\\* Handshake has failed"
179 "*Regular expression indicating failed TLS handshake.
180The default is what GnuTLS's \"gnutls-cli\" outputs."
181 ;; GnuTLS cli.c:do_handshake() prints this string on failure. If the
182 ;; logic, or the string itself, is modified, this must be updated.
183 :version "22.1"
184 :type 'regexp
185 :group 'starttls)
186
187(defcustom starttls-success "- Compression: "
188 "*Regular expression indicating completed TLS handshakes.
189The default is what GnuTLS's \"gnutls-cli\" outputs."
190 ;; GnuTLS cli.c:do_handshake() calls, on success,
191 ;; common.c:print_info(), that unconditionally print this string
192 ;; last. If that logic, or the string itself, is modified, this
193 ;; must be updated.
194 :version "22.1"
195 :type 'regexp
196 :group 'starttls)
197
198(defun starttls-negotiate-gnutls (process)
199 "Negotiate TLS on PROCESS opened by `open-starttls-stream'.
200This should typically only be done once. It typically returns a
201multi-line informational message with information about the
202handshake, or nil on failure."
203 (let (buffer info old-max done-ok done-bad)
204 (if (null (setq buffer (process-buffer process)))
205 ;; XXX How to remove/extract the TLS negotiation junk?
206 (signal-process (process-id process) 'SIGALRM)
207 (with-current-buffer buffer
208 (save-excursion
209 (setq old-max (goto-char (point-max)))
210 (signal-process (process-id process) 'SIGALRM)
211 (while (and (processp process)
212 (eq (process-status process) 'run)
213 (save-excursion
214 (goto-char old-max)
215 (not (or (setq done-ok (re-search-forward
216 starttls-success nil t))
217 (setq done-bad (re-search-forward
218 starttls-failure nil t))))))
219 (accept-process-output process 1 100)
220 (sit-for 0.1))
221 (setq info (buffer-substring-no-properties old-max (point-max)))
222 (delete-region old-max (point-max))
223 (if (or (and done-ok (not done-bad))
224 ;; Prevent mitm that fake success msg after failure msg.
225 (and done-ok done-bad (< done-ok done-bad)))
226 info
227 (message "STARTTLS negotiation failed: %s" info)
228 nil))))))
229
230(defun starttls-negotiate (process)
231 (if starttls-use-gnutls
232 (starttls-negotiate-gnutls process)
233 (signal-process (process-id process) 'SIGALRM)))
234
235(defun starttls-open-stream-gnutls (name buffer host port)
236 (message "Opening STARTTLS connection to `%s:%s'..." host port)
237 (let* (done
238 (old-max (with-current-buffer buffer (point-max)))
239 (process-connection-type starttls-process-connection-type)
240 (process (apply #'start-process name buffer
241 starttls-gnutls-program "-s" host
242 "-p" (if (integerp port)
243 (int-to-string port)
244 port)
245 starttls-extra-arguments)))
246 (set-process-query-on-exit-flag process nil)
247 (while (and (processp process)
248 (eq (process-status process) 'run)
249 (with-current-buffer buffer
250 (goto-char old-max)
251 (not (setq done (re-search-forward
252 starttls-connect nil t)))))
253 (accept-process-output process 0 100)
254 (sit-for 0.1))
255 (if done
256 (with-current-buffer buffer
257 (delete-region old-max done))
258 (delete-process process)
259 (setq process nil))
260 (message "Opening STARTTLS connection to `%s:%s'...%s"
261 host port (if done "done" "failed"))
262 process))
263
264;;;###autoload
265(defun starttls-open-stream (name buffer host port)
266 "Open a TLS connection for a port to a host.
267Returns a subprocess object to represent the connection.
268Input and output work as for subprocesses; `delete-process' closes it.
269Args are NAME BUFFER HOST PORT.
270NAME is name for process. It is modified if necessary to make it unique.
271BUFFER is the buffer (or `buffer-name') to associate with the process.
272 Process output goes at end of that buffer, unless you specify
273 an output stream or filter function to handle the output.
274 BUFFER may be also nil, meaning that this process is not associated
275 with any buffer
276Third arg is name of the host to connect to, or its IP address.
277Fourth arg PORT is an integer specifying a port to connect to.
278If `starttls-use-gnutls' is nil, this may also be a service name, but
279GnuTLS requires a port number."
280 (if starttls-use-gnutls
281 (starttls-open-stream-gnutls name buffer host port)
282 (message "Opening STARTTLS connection to `%s:%s'" host (format "%s" port))
283 (let* ((process-connection-type starttls-process-connection-type)
284 (process (apply #'start-process
285 name buffer starttls-program
286 host (format "%s" port)
287 starttls-extra-args)))
288 (set-process-query-on-exit-flag process nil)
289 process)))
290
291(defun starttls-available-p ()
292 "Say whether the STARTTLS programs are available."
293 (and (not (memq system-type '(windows-nt ms-dos)))
294 (executable-find (if starttls-use-gnutls
295 starttls-gnutls-program
296 starttls-program))))
297
298(defalias 'starttls-any-program-available 'starttls-available-p)
299(make-obsolete 'starttls-any-program-available 'starttls-available-p
300 "2011-08-02")
301
302(provide 'starttls)
303
304;;; starttls.el ends here