Commit cbc56136 cbc5613658f4ccba5e8ccc5bbae07db83a9bf0e4 by Sergey Poznyakoff

Remove the Scheme implementation of the Sieve language.

* NEWS: Describe the change.
* doc/texinfo/mailutils.texi: Remove description of sieve2scm
* doc/texinfo/programs.texi: Likewise.
* scheme/Makefile.am: Remove sieve2scm.
* scheme/mimeheader.scm: Remove.
* scheme/numaddr.scm: Remove.
* scheme/redirect.scm: Remove.
* scheme/reject.scm: Remove.
* scheme/sieve-core.scm: Remove.
* scheme/sieve2scm.scmi: Remove.
* scheme/vacation.scm: Remove.
1 parent ecbf0d3a
1 GNU mailutils NEWS -- history of user-visible changes. 2017-04-08 1 GNU mailutils NEWS -- history of user-visible changes. 2017-04-09
2 Copyright (C) 2002-2017 Free Software Foundation, Inc. 2 Copyright (C) 2002-2017 Free Software Foundation, Inc.
3 See the end of file for copying conditions. 3 See the end of file for copying conditions.
4 4
...@@ -88,6 +88,15 @@ defined. Instead, the following constants are defined in config.h: ...@@ -88,6 +88,15 @@ defined. Instead, the following constants are defined in config.h:
88 88
89 * movemail: new option --progress-meter 89 * movemail: new option --progress-meter
90 90
91 * scheme implementation of the Sieve language discontinued
92
93 There's no reason to keep two different implementations of the Sieve
94 language within the same package. The principal implementation
95 (libmu_sieve) is faster, much more advanced and rich in features than
96 the implementation in Scheme. The decision has therefore been taken to
97 discontinue the latter and to concentrate all efforts on the further
98 development of the former.
99
91 100
92 Version 3.2 - 2017-03-11 101 Version 3.2 - 2017-03-11
93 102
......
...@@ -257,7 +257,6 @@ Reading Mail ...@@ -257,7 +257,6 @@ Reading Mail
257 @command{sieve} 257 @command{sieve}
258 258
259 * sieve interpreter:: A Sieve Interpreter 259 * sieve interpreter:: A Sieve Interpreter
260 * sieve2scm:: A Sieve to Scheme Translator and Filter
261 260
262 A Sieve Interpreter 261 A Sieve Interpreter
263 262
......
...@@ -5902,14 +5902,11 @@ only the first. ...@@ -5902,14 +5902,11 @@ only the first.
5902 @UNREVISED 5902 @UNREVISED
5903 5903
5904 Sieve is a language for filtering e-mail messages at time of final 5904 Sieve is a language for filtering e-mail messages at time of final
5905 delivery, described in RFC 3028. GNU Mailutils provides two 5905 delivery, described in RFC 3028. GNU Mailutils contains
5906 implementations of this language: a stand-alone @dfn{sieve interpreter} 5906 stand-alone @dfn{sieve interpreter}, which is described in detail below.
5907 and a @dfn{sieve translator and filter}. The following sections describe these
5908 utilities in detail.
5909 5907
5910 @menu 5908 @menu
5911 * sieve interpreter:: A Sieve Interpreter 5909 * sieve interpreter:: A Sieve Interpreter
5912 * sieve2scm:: A Sieve to Scheme Translator and Filter
5913 @end menu 5910 @end menu
5914 5911
5915 @node sieve interpreter 5912 @node sieve interpreter
...@@ -6250,41 +6247,6 @@ source for the required action NAME is not available ...@@ -6250,41 +6247,6 @@ source for the required action NAME is not available
6250 @end enumerate 6247 @end enumerate
6251 6248
6252 @c *********************************************************************** 6249 @c ***********************************************************************
6253
6254 @page
6255 @node sieve2scm
6256 @subsection A Sieve to Scheme Translator and Filter
6257 @UNREVISED
6258
6259 A Sieve to Scheme Translator @command{sieve2scm} translates a given
6260 Sieve script into an equivalent Scheme program and optionally executes
6261 it. The program itself is written in Scheme and requires presence of
6262 Guile version 1.8 or newer on the system. For more information on
6263 Guile refer to @ref{Top,,Overview,guile,The Guile Reference Manual}.
6264
6265 @table @option
6266 @item -f @var{filename}
6267 @itemx --file @var{filename}
6268 Set input file name.
6269
6270 @item -o @var{filename}
6271 @itemx --output @var{filename}
6272 Set output file name
6273
6274 @item -L @var{dirname}
6275 @itemx --lib-dir @var{dirname}
6276 Set sieve library directory name
6277
6278 @item -d @var{level}
6279 @itemx --debug @var{level}
6280 Set debugging level
6281 @end table
6282
6283 The Scheme programs produced by @command{sieve2scm} can be used with
6284 @command{guimb} or @command{maidag}.
6285
6286 @c ***********************************************************************
6287
6288 @page 6250 @page
6289 @node guimb 6251 @node guimb
6290 @section @command{guimb} --- A Mailbox Scanning and Processing Language 6252 @section @command{guimb} --- A Mailbox Scanning and Processing Language
......
...@@ -15,19 +15,7 @@ ...@@ -15,19 +15,7 @@
15 ## You should have received a copy of the GNU General Public License 15 ## You should have received a copy of the GNU General Public License
16 ## along with GNU Mailutils. If not, see <http://www.gnu.org/licenses/>. 16 ## along with GNU Mailutils. If not, see <http://www.gnu.org/licenses/>.
17 17
18 bin_SCRIPTS = sieve2scm guimb 18 bin_SCRIPTS = guimb
19
20 # FIXME: Sieve2scm is temporarly exempted from installchecks because
21 # it may fail starting during checks, if libguile-mailutils-v- library
22 # has not been previously installed. The proper fix would be to alter
23 # %load-path during tests.
24 AM_INSTALLCHECK_STD_OPTIONS_EXEMPT=sieve2scm
25
26 sievemoddir=@MU_GUILE_SIEVE_MOD_DIR@
27
28 sieve2scm: sieve2scm.scmi package.sed
29 $(AM_V_GEN)sed -f package.sed $(srcdir)/sieve2scm.scmi > sieve2scm
30 $(AM_V_at)chmod +w sieve2scm
31 19
32 guimb: guimb.scmi package.sed 20 guimb: guimb.scmi package.sed
33 $(AM_V_GEN)sed -f package.sed $(srcdir)/guimb.scmi > guimb 21 $(AM_V_GEN)sed -f package.sed $(srcdir)/guimb.scmi > guimb
...@@ -37,26 +25,12 @@ package.sed: Makefile ...@@ -37,26 +25,12 @@ package.sed: Makefile
37 $(AM_V_GEN)echo 's,%GUILE_BINDIR%,@GUILE_BINDIR@,g' > package.sed 25 $(AM_V_GEN)echo 's,%GUILE_BINDIR%,@GUILE_BINDIR@,g' > package.sed
38 $(AM_V_at)echo 's,%BINDIR%,$(bindir),g' >> package.sed 26 $(AM_V_at)echo 's,%BINDIR%,$(bindir),g' >> package.sed
39 $(AM_V_at)echo 's,%GUILE_SITE%,$(GUILE_SITE),g' >> package.sed 27 $(AM_V_at)echo 's,%GUILE_SITE%,$(GUILE_SITE),g' >> package.sed
40 $(AM_V_at)echo 's,%LIBDIR%,$(sievemoddir),g' >> package.sed
41 $(AM_V_at)echo 's,%PACKAGE%,$(PACKAGE),g' >> package.sed 28 $(AM_V_at)echo 's,%PACKAGE%,$(PACKAGE),g' >> package.sed
42 $(AM_V_at)echo 's,%VERSION%,$(VERSION),g' >> package.sed 29 $(AM_V_at)echo 's,%VERSION%,$(VERSION),g' >> package.sed
43 30
44 CLEANFILES = sieve2scm guimb package.sed 31 CLEANFILES = guimb package.sed
45
46 sitedir=@GUILE_SITE@/$(PACKAGE)
47 site_DATA=sieve-core.scm
48
49 sievemod_DATA=\
50 mimeheader.scm\
51 numaddr.scm\
52 redirect.scm\
53 reject.scm\
54 vacation.scm
55 32
56 EXTRA_DIST=\ 33 EXTRA_DIST=\
57 $(sievemod_DATA)\
58 sieve-core.scm\
59 sieve2scm.scmi\
60 guimb.scmi 34 guimb.scmi
61 35
62 installcheck-binSCRIPTS: $(bin_SCRIPTS) 36 installcheck-binSCRIPTS: $(bin_SCRIPTS)
......
1 ;;;; GNU Mailutils -- a suite of utilities for electronic mail
2 ;;;; Copyright (C) 1999-2001, 2007, 2010-2012, 2014-2017 Free Software
3 ;;;; Foundation, Inc.
4 ;;;;
5 ;;;; GNU Mailutils is free software; you can redistribute it and/or modify
6 ;;;; it under the terms of the GNU General Public License as published by
7 ;;;; the Free Software Foundation; either version 3, or (at your option)
8 ;;;; any later version.
9 ;;;;
10 ;;;; GNU Mailutils is distributed in the hope that it will be useful,
11 ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 ;;;; GNU General Public License for more details.
14 ;;;;
15 ;;;; You should have received a copy of the GNU General Public License along
16 ;;;; with GNU Mailutils. If not, see <http://www.gnu.org/licenses/>.
17
18 ;;;; This module provides GNU extension test "mimeheader".
19
20 ;;;; Syntax: mimeheader [COMPARATOR] [MATCH-TYPE]
21 ;;;; <header-names: string-list> <key-list: string-list>
22 ;;;;
23 ;;;; The "mimeheader" test evaluates to true if in any part of the
24 ;;;; multipart MIME message a header name from <header-names> list
25 ;;;; matches any key from <key-list>. If the message is not multipart,
26 ;;;; "mimeheader" test is equivalent to "header" test.
27 ;;;;
28 ;;;; The arguments to "mimeheader" test are the same as to "header" test.
29
30 ;;;; Example:
31 ;;;;
32 ;;;; require [ "mimeheader", "reject"];
33 ;;;; if mimeheader :matches "Content-Type" "*application/msword;*" {
34 ;;;; reject "Please do not send data in a proprietary format.";
35 ;;;; }
36
37 (define (test-mimeheader header-list key-list . opt-args)
38 (if (mu-message-multipart? sieve-current-message)
39 (let ((mime-count (mu-message-get-num-parts sieve-current-message))
40 (comp (find-comp opt-args))
41 (match (find-match opt-args)))
42 (call-with-current-continuation
43 (lambda (exit)
44 (do ((n 1 (1+ n)))
45 ((> n mime-count) #f)
46 (let ((msg (mu-message-get-part sieve-current-message n)))
47 (if msg
48 (for-each
49 (lambda (key)
50 (let ((header-fields (mu-message-get-header-fields
51 msg
52 header-list))
53 (rx (if (eq? match #:matches)
54 (make-regexp (sieve-regexp-to-posix key)
55 (if (eq? comp string-ci=?)
56 regexp/icase
57 '()))
58 #f)))
59 (for-each
60 (lambda (h)
61 (let ((hdr (cdr h)))
62 (if hdr
63 (case match
64 ((#:is)
65 (if (comp hdr key)
66 (exit #t)))
67 ((#:contains)
68 (if (sieve-str-str hdr key comp)
69 (exit #t)))
70 ((#:matches)
71 (if (regexp-exec rx hdr)
72 (exit #t)))))))
73 header-fields)))
74 key-list)
75 #f))))))
76 (apply test-header header-list key-list opt-args)))
77
78 ;;; Register the test at compile time
79 (if sieve-parser
80 (sieve-register-test "mimeheader"
81 test-mimeheader
82 (list 'string-list 'string-list)
83 (append comparator match-type)))
1 ;;;; GNU Mailutils -- a suite of utilities for electronic mail
2 ;;;; Copyright (C) 1999-2001, 2007, 2010-2012, 2014-2017 Free Software
3 ;;;; Foundation, Inc.
4 ;;;;
5 ;;;; GNU Mailutils is free software; you can redistribute it and/or modify
6 ;;;; it under the terms of the GNU General Public License as published by
7 ;;;; the Free Software Foundation; either version 3, or (at your option)
8 ;;;; any later version.
9 ;;;;
10 ;;;; GNU Mailutils is distributed in the hope that it will be useful,
11 ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 ;;;; GNU General Public License for more details.
14 ;;;;
15 ;;;; You should have received a copy of the GNU General Public License along
16 ;;;; with GNU Mailutils. If not, see <http://www.gnu.org/licenses/>.
17
18 ;;;; This module provides GNU extension test "numaddr".
19
20 ;;;; Syntax: numaddr [":over" / ":under"] <header-names: string-list>
21 ;;;; <limit: number>
22 ;;;; The "numaddr" test counts Internet addresses in structured headers
23 ;;;; that contain addresses. It returns true if the total number of
24 ;;;; addresses satisfies the requested relation:
25 ;;;;
26 ;;;; If the argument is ":over" and the number of addresses is greater than
27 ;;;; the number provided, the test is true; otherwise, it is false.
28 ;;;;
29 ;;;; If the argument is ":under" and the number of addresses is less than
30 ;;;; the number provided, the test is true; otherwise, it is false.
31 ;;;;
32 ;;;; If the argument is empty, ":over" is assumed.
33
34 ;;;; Example:
35 ;;;;
36 ;;;; require [ "numaddr" ];
37 ;;;; if numaddr :over [ "To", "Cc" ] 50 { discard; }
38
39 (define (test-numaddr header-list count . comp)
40 (let ((total-count 0)
41 (header-fields (mu-message-get-header-fields
42 sieve-current-message
43 header-list))
44 (compfun (cond
45 ((or (null? (car comp)) (eq? (car comp) #:over))
46 (lambda (val lim)
47 (> val lim)))
48 ((eq? (car comp) #:under)
49 (lambda (val lim)
50 (< val lim)))
51 (else
52 (runtime-message SIEVE-ERROR
53 "test-numaddr: unknown comparator "
54 comp)))))
55 (call-with-current-continuation
56 (lambda (exit)
57 (for-each
58 (lambda (h)
59 (let ((hdr (cdr h)))
60 (if hdr
61 (let ((naddr (mu-address-get-count hdr)))
62 (set! total-count (+ total-count naddr))
63 (if (compfun total-count count)
64 (exit #t))))))
65 header-fields)
66 #f))))
67
68 ;;; Register the test at compile time
69 (if sieve-parser
70 (sieve-register-test "numaddr"
71 test-numaddr
72 (list 'string-list 'number)
73 size-comp))
1 ;;;; GNU Mailutils -- a suite of utilities for electronic mail
2 ;;;; Copyright (C) 1999-2001, 2006-2007, 2010-2012, 2014-2017 Free
3 ;;;; Software Foundation, Inc.
4 ;;;;
5 ;;;; GNU Mailutils is free software; you can redistribute it and/or modify
6 ;;;; it under the terms of the GNU General Public License as published by
7 ;;;; the Free Software Foundation; either version 3, or (at your option)
8 ;;;; any later version.
9 ;;;;
10 ;;;; GNU Mailutils is distributed in the hope that it will be useful,
11 ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 ;;;; GNU General Public License for more details.
14 ;;;;
15 ;;;; You should have received a copy of the GNU General Public License along
16 ;;;; with GNU Mailutils. If not, see <http://www.gnu.org/licenses/>.
17
18 ;;;; This module provides sieve's "redirect" action.
19
20 ;;; rfc3028 says:
21 ;;; "Implementations SHOULD take measures to implement loop control,"
22 ;;; We do this by appending an "X-Sender" header to each message
23 ;;; being redirected. If one of the "X-Sender" headers of the message
24 ;;; contains our email address, we assume it is a loop and bail out.
25
26 (define (sent-from-me? msg)
27 (call-with-current-continuation
28 (lambda (exit)
29 (for-each
30 (lambda (hdr)
31 (if (and (string-ci=? (car hdr) "X-Sender")
32 (string-ci=? (mu-address-get-email (cdr hdr))
33 sieve-my-email))
34 (exit #t)))
35 (mu-message-get-header-fields sieve-current-message))
36 #f)))
37
38 ;;; redirect action
39 (define (action-redirect address)
40 (sieve-verbose-print "REDIRECT" "to address " address)
41 (handle-exception
42 (if sieve-my-email
43 (cond
44 ((sent-from-me? sieve-current-message)
45 (runtime-message SIEVE-WARNING "Redirection loop detected"))
46 (else
47 (let ((out-msg (mu-message-copy sieve-current-message))
48 (sender (mu-message-get-sender sieve-current-message)))
49 (mu-message-set-header out-msg "X-Sender" sieve-my-email)
50 (mu-message-send out-msg #f sender address)
51 (mu-message-destroy out-msg))
52 (mu-message-delete sieve-current-message))))))
53
54 ;;; Register action
55 (if sieve-parser
56 (sieve-register-action "redirect" action-redirect (list 'string) '()))
57
58
59
1 ;;;; GNU Mailutils -- a suite of utilities for electronic mail
2 ;;;; Copyright (C) 1999-2001, 2006-2007, 2010-2012, 2014-2017 Free
3 ;;;; Software Foundation, Inc.
4 ;;;;
5 ;;;; GNU Mailutils is free software; you can redistribute it and/or modify
6 ;;;; it under the terms of the GNU General Public License as published by
7 ;;;; the Free Software Foundation; either version 3, or (at your option)
8 ;;;; any later version.
9 ;;;;
10 ;;;; GNU Mailutils is distributed in the hope that it will be useful,
11 ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 ;;;; GNU General Public License for more details.
14 ;;;;
15 ;;;; You should have received a copy of the GNU General Public License along
16 ;;;; with GNU Mailutils. If not, see <http://www.gnu.org/licenses/>.
17
18 ;;;; This module provides sieve's "reject" action.
19
20 (define sieve-option-quote #t)
21
22 (define (action-reject reason)
23 (sieve-verbose-print "REJECT")
24 (handle-exception
25 (let ((mime (mu-mime-create 0))
26 (datestr (strftime "%a, %b %d %H:%M:%S %Y %Z"
27 (localtime (current-time))))
28 (sender (mu-message-get-sender sieve-current-message)))
29 (let* ((mesg (mu-message-create))
30 (port (mu-message-get-port mesg "w")))
31
32 (display "The original message was received at " port)
33 (display datestr port)
34 (newline port)
35 (display "from " port)
36 (display sender port)
37 (display ".\n" port)
38
39 (display "Message was refused by recipient's mail filtering program.\n"
40 port)
41 (display "Reason given was as follows:\n" port)
42 (newline port)
43 (display reason port)
44
45 (close-output-port port)
46 (mu-mime-add-part mime mesg))
47
48 ;; message/delivery-status
49 (let* ((mesg (mu-message-create))
50 (port (mu-message-get-port mesg "w")))
51 (mu-message-set-header mesg "Content-Type" "message/delivery-status")
52
53 (display (string-append "Reporting-UA: sieve; "
54 mu-package-string "\n") port)
55 (display (string-append "Arrival-Date: " datestr "\n") port)
56 (newline port)
57
58 (display (string-append "Final-Recipient: RFC822; " sieve-my-email "\n")
59 port)
60
61 (display "Action: deleted\n" port);
62 (display "Disposition: automatic-action/MDN-sent-automatically;deleted\n"
63 port)
64 (display (string-append
65 "Last-Attempt-Date: " datestr "\n") port)
66
67 (close-output-port port)
68 (mu-mime-add-part mime mesg))
69
70 ;; Quote original message
71 (let* ((mesg (mu-message-create))
72 (port (mu-message-get-port mesg "w"))
73 (in-port (mu-message-get-port sieve-current-message "r" #t)))
74 (mu-message-set-header mesg "Content-Type" "message/rfc822")
75
76 (do ((line (read-line in-port) (read-line in-port)))
77 ((eof-object? line) #f)
78 (display line port)
79 (newline port))
80
81 (close-input-port in-port)
82 (close-output-port port)
83 (mu-mime-add-part mime mesg))
84
85 (mu-message-send (mu-mime-get-message mime) #f sieve-daemon-email sender)
86 (mu-message-delete sieve-current-message))))
87
88 ;;; Register action
89 (if sieve-parser
90 (sieve-register-action "reject" action-reject (list 'string) '()))
91
92
93
94
95
1 ;;;; GNU Mailutils -- a suite of utilities for electronic mail
2 ;;;; Copyright (C) 1999-2001, 2006-2007, 2010-2012, 2014-2017 Free
3 ;;;; Software Foundation, Inc.
4 ;;;;
5 ;;;; GNU Mailutils is free software; you can redistribute it and/or modify
6 ;;;; it under the terms of the GNU General Public License as published by
7 ;;;; the Free Software Foundation; either version 3, or (at your option)
8 ;;;; any later version.
9 ;;;;
10 ;;;; GNU Mailutils is distributed in the hope that it will be useful,
11 ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 ;;;; GNU General Public License for more details.
14 ;;;;
15 ;;;; You should have received a copy of the GNU General Public License along
16 ;;;; with GNU Mailutils. If not, see <http://www.gnu.org/licenses/>.
17
18 ;;;; This module provides core functionality for the sieve scripts.
19
20 (define-module (mailutils sieve-core))
21
22 (use-modules (mailutils mailutils))
23
24 ;;; Set to #t when parsing
25 (define-public sieve-parser #f)
26
27 ;;; Name of the input source
28 (define-public sieve-source "UNKNOWN")
29
30 ;;; The email address for originator of error messages. Should be <>
31 ;;; but current mailutils API is unable to parse and handle it.
32 ;;; Site administrators are supposed to replace it with the
33 ;;; actual value.
34 (define-public sieve-daemon-email "MAILER-DAEMON@localhost")
35
36 ;;; The email address of the user whose mailbox is being processed.
37 ;;; If #f, it will be set by sieve-main
38 (define-public sieve-my-email #f)
39
40 (define SIEVE-WARNING "Warning")
41 (define SIEVE-ERROR "Error")
42 (define SIEVE-NOTICE "Notice")
43
44 (defmacro handle-exception (. expr)
45 `(catch 'mailutils-error
46 (lambda () ,@expr)
47 (lambda (key . args)
48 (runtime-message SIEVE-ERROR
49 "In function " (car args) ": "
50 (apply format #f
51 (list-ref args 1) (list-ref args 2))
52 (let ((error-code
53 (car (list-ref args (1- (length args))))))
54 (if (= error-code 0)
55 ""
56 (string-append
57 "; Error code: "
58 (number->string error-code)
59 " - "
60 (mu-strerror error-code))))))))
61
62 ;;; Set to #t if verbose action listing is requested
63 (define-public sieve-verbose #f)
64
65 (defmacro sieve-verbose-print (action . rest)
66 `(if sieve-verbose
67 (let ((uid (false-if-exception
68 (mu-message-get-uid sieve-current-message))))
69 (display ,action)
70 (display " on msg uid ")
71 (display uid)
72 (let ((args (list ,@rest)))
73 (cond ((not (null? args))
74 (display ": ")
75 (for-each
76 display
77 args))))
78 (newline))))
79
80 ;;; List of open mailboxes.
81 ;;; Each entry is: (list MAILBOX-NAME OPEN-FLAGS MBOX)
82 (define sieve-mailbox-list '())
83
84 ;;; Cached mailbox open: Lookup in the list first, if not found,
85 ;;; call mu-mailbox-open and append to the list.
86 ;;; NOTE: second element of each slot (OPEN-FLAGS) is not currently
87 ;;; used, sinse all the mailboxes are open with "cw".
88 (define (sieve-mailbox-open name flags)
89 (let ((slot (assoc name sieve-mailbox-list)))
90 (if slot
91 (list-ref slot 2)
92 (let ((mbox (false-if-exception (mu-mailbox-open name flags))))
93 (if mbox
94 (set! sieve-mailbox-list (append
95 sieve-mailbox-list
96 (list
97 (list name flags mbox)))))
98 mbox))))
99
100 ;;; Close all open mailboxes.
101 (define (sieve-close-mailboxes)
102 (for-each
103 (lambda (slot)
104 (cond
105 ((list-ref slot 2)
106 => (lambda (mbox)
107 (mu-mailbox-close mbox)))))
108 sieve-mailbox-list)
109 (set! sieve-mailbox-list '()))
110
111 (define (sieve-expand-filename filename)
112 (case (string-ref filename 0)
113 ((#\/ #\% #\~ #\+ #\=)
114 filename)
115 (else
116 (let ((pw (getpwuid (geteuid))))
117 (if (vector? pw)
118 (string-append (vector-ref pw 5)
119 "/"
120 filename)
121 filename)))))
122
123 ;;; Comparators
124 (define-public sieve-standard-comparators
125 (list (list "i;octet" string=?)
126 (list "i;ascii-casemap" string-ci=?)))
127
128 ;;; Stop statement
129
130 (define-public (sieve-stop)
131 (sieve-verbose-print "STOP")
132 (throw 'sieve-stop))
133
134 ;;; Basic five actions:
135
136 ;;; reject is defined in reject.scm
137
138 ;;; fileinto
139
140 (define-public (action-fileinto filename)
141 (let ((name (sieve-expand-filename filename)))
142 (sieve-verbose-print "FILEINTO" "delivering into " name)
143 (if (string? name)
144 (let ((outbox (sieve-mailbox-open name "cw")))
145 (cond
146 (outbox
147 (handle-exception
148 (mu-mailbox-append-message outbox sieve-current-message)
149 (mu-message-delete sieve-current-message)))
150 (else
151 (runtime-message SIEVE-ERROR
152 "Could not open mailbox " name))))
153 (runtime-message SIEVE-ERROR
154 "Could not expand mailbox name " filename))))
155
156 ;;; redirect is defined in redirect.scm
157
158 ;;; keep -- does nothing worth mentioning :^)
159
160 (define-public (action-keep)
161 (sieve-verbose-print "KEEP")
162 (handle-exception
163 (mu-message-delete sieve-current-message #f)))
164
165 ;;; discard
166
167 (define-public (action-discard)
168 (sieve-verbose-print "DISCARD" "marking as deleted")
169 (handle-exception
170 (mu-message-delete sieve-current-message)))
171
172 ;;; Register standard actions
173 (define-public sieve-standard-actions
174 (list (list "keep" action-keep '() '())
175 (list "discard" action-discard '() '())
176 (list "fileinto" action-fileinto (list 'string) '())))
177
178 ;;; Some utilities.
179
180 (define (sieve-get-opt-arg opt-args tag default)
181 (cond
182 ((member tag opt-args) =>
183 (lambda (x)
184 (car (cdr x))))
185 (else
186 default)))
187
188 (define (find-comp opt-args)
189 (sieve-get-opt-arg opt-args #:comparator string-ci=?))
190
191 (define (find-match opt-args)
192 (cond
193 ((member #:is opt-args)
194 #:is)
195 ((member #:contains opt-args)
196 #:contains)
197 ((member #:matches opt-args)
198 #:matches)
199 ((member #:regex opt-args)
200 #:regex)
201 (else
202 #:is)))
203
204 (define (sieve-str-str str key comp)
205 (if (string-null? key)
206 ;; rfc3028:
207 ;; If a header listed in the header-names argument exists, it contains
208 ;; the null key (""). However, if the named header is not present, it
209 ;; does not contain the null key.
210 ;; This function gets called only if the header was present. So:
211 #t
212 (let* ((char (string-ref key 0))
213 (str-len (string-length str))
214 (key-len (string-length key))
215 (limit (- str-len key-len)))
216 (if (< limit 0)
217 #f
218 (call-with-current-continuation
219 (lambda (xx)
220 (do ((index 0 (1+ index)))
221 ((cond
222 ((> index limit)
223 #t)
224 ;; FIXME: This is very inefficient, but I have to use this
225 ;; provided (string-index str (string-ref key 0)) may not
226 ;; work...
227 ((comp (substring str index (+ index key-len))
228 key)
229 (xx #t))
230 (else
231 #f)) #f))
232 #f))))))
233
234 ;;; Convert sieve-style regexps to POSIX:
235
236 (define (sieve-regexp-to-posix regexp)
237 (let ((length (string-length regexp)))
238 (do ((cl '())
239 (escape #f)
240 (i 0 (1+ i)))
241 ((= i length) (list->string (reverse cl)))
242 (let ((ch (string-ref regexp i)))
243 (cond
244 (escape
245 (set! cl (append (list ch) cl))
246 (set! escape #f))
247 ((char=? ch #\\)
248 (set! escape #t))
249 ((char=? ch #\?)
250 (set! cl (append (list #\.) cl)))
251 ((char=? ch #\*)
252 (set! cl (append (list #\* #\.) cl)))
253 ((member ch (list #\. #\$ #\^ #\[ #\]))
254 (set! cl (append (list ch #\\) cl)))
255 (else
256 (set! cl (append (list ch) cl))))))))
257
258
259 (define (get-regex match key comp)
260 (case match
261 ((#:matches)
262 (make-regexp (sieve-regexp-to-posix key)
263 (if (eq? comp string-ci=?)
264 regexp/icase
265 '())))
266 ((#:regex)
267 (make-regexp key
268 (if (eq? comp string-ci=?)
269 regexp/icase
270 '())))
271 (else
272 #f)))
273
274 ;;;; Standard tests:
275
276 (define-public (test-address header-list key-list . opt-args)
277 (let ((comp (find-comp opt-args))
278 (match (find-match opt-args))
279 (part (cond
280 ((member #:localpart opt-args)
281 #:localpart)
282 ((member #:domain opt-args)
283 #:domain)
284 (else
285 #:all))))
286 (call-with-current-continuation
287 (lambda (exit)
288 (for-each
289 (lambda (key)
290 (let ((header-fields (mu-message-get-header-fields
291 sieve-current-message
292 header-list))
293 (rx (get-regex match key comp)))
294 (for-each
295 (lambda (h)
296 (let ((hdr (cdr h)))
297 (if hdr
298 (let ((naddr (mu-address-get-count hdr)))
299 (do ((n 1 (1+ n)))
300 ((> n naddr) #f)
301 (let ((addr (case part
302 ((#:all)
303 (mu-address-get-email hdr n))
304 ((#:localpart)
305 (mu-address-get-local hdr n))
306 ((#:domain)
307 (mu-address-get-domain hdr n)))))
308 (if addr
309 (case match
310 ((#:is)
311 (if (comp addr key)
312 (exit #t)))
313 ((#:contains)
314 (if (sieve-str-str addr key comp)
315 (exit #t)))
316 ((#:matches #:regex)
317 (if (regexp-exec rx addr)
318 (exit #t))))
319 (runtime-message SIEVE-NOTICE
320 "Can't get address parts for message "
321 sieve-current-message))))))))
322 header-fields)))
323 key-list)
324 #f))))
325
326 (define-public (test-size key-size . comp)
327 (let ((size (mu-message-get-size sieve-current-message)))
328 (cond
329 ((null? comp) ;; An extension.
330 (= size key-size))
331 ((eq? (car comp) #:over)
332 (> size key-size))
333 ((eq? (car comp) #:under)
334 (< size key-size))
335 (else
336 (runtime-message SIEVE-ERROR "test-size: unknown comparator " comp)))))
337
338 (define-public (test-envelope part-list key-list . opt-args)
339 (let ((comp (find-comp opt-args))
340 (match (find-match opt-args)))
341 (call-with-current-continuation
342 (lambda (exit)
343 (for-each
344 (lambda (part)
345 (cond
346 ((string-ci=? part "From")
347 (let ((sender (mu-message-get-sender sieve-current-message)))
348 (for-each
349 (lambda (key)
350 (if (comp key sender)
351 (exit #t)))
352 key-list)))
353 (else
354 (runtime-message SIEVE-ERROR
355 "Envelope part " part " not supported")
356 #f)))
357 part-list)
358 #f))))
359
360 (define-public (test-exists header-list)
361 (call-with-current-continuation
362 (lambda (exit)
363 (for-each (lambda (hdr)
364 (let ((val (mu-message-get-header sieve-current-message hdr)))
365 (if (or (not val) (= (string-length val) 0))
366 (exit #f))))
367 header-list)
368 #t)))
369
370 (define-public (test-header header-list key-list . opt-args)
371 (let ((comp (find-comp opt-args))
372 (match (find-match opt-args)))
373 (call-with-current-continuation
374 (lambda (exit)
375 (for-each
376 (lambda (key)
377 (let ((header-fields (mu-message-get-header-fields
378 sieve-current-message
379 header-list))
380 (rx (get-regex match key comp)))
381 (for-each
382 (lambda (h)
383 (let ((hdr (cdr h)))
384 (if hdr
385 (case match
386 ((#:is)
387 (if (comp hdr key)
388 (exit #t)))
389 ((#:contains)
390 (if (sieve-str-str hdr key comp)
391 (exit #t)))
392 ((#:matches #:regex)
393 (if (regexp-exec rx hdr)
394 (exit #t)))))))
395 header-fields)))
396 key-list)
397 #f))))
398
399 ;;; Register tests:
400 (define address-part (list (cons "localpart" #f)
401 (cons "domain" #f)
402 (cons "all" #f)))
403 (define match-type (list (cons "is" #f)
404 (cons "contains" #f)
405 (cons "matches" #f)
406 (cons "regex" #f)))
407 (define size-comp (list (cons "under" #f)
408 (cons "over" #f)))
409 (define comparator (list (cons "comparator" 'string)))
410
411 (define-public sieve-standard-tests
412 (list
413 (list "address"
414 test-address
415 (list 'string-list 'string-list)
416 (append address-part comparator match-type))
417 (list "size"
418 test-size
419 (list 'number)
420 size-comp)
421 (list "envelope"
422 test-envelope
423 (list 'string-list 'string-list)
424 (append comparator address-part match-type))
425 (list "exists"
426 test-exists
427 (list 'string-list)
428 '())
429 (list "header"
430 test-header
431 (list 'string-list 'string-list)
432 (append comparator match-type))
433 (list "false" #f '() '())
434 (list "true" #t '() '())))
435
436 ;;; runtime-message
437
438 (define-public (runtime-message level . text)
439 (let ((msg (apply string-append
440 (map (lambda (x)
441 (format #f "~A" x))
442 (append
443 (list "(in " sieve-source ") ")
444 text)))))
445 (if sieve-current-message
446 (mu-message-set-header sieve-current-message
447 (string-append "X-Sieve-" level)
448 msg))
449 (if (isatty? (current-error-port))
450 (display (string-append level ": " msg "\n") (current-error-port)))))
451
452 ;;; Sieve-main
453 (define-public sieve-mailbox #f)
454 (define-public sieve-current-message #f)
455
456 (define-public (sieve-run-current-message thunk)
457 (and (catch 'sieve-stop
458 thunk
459 (lambda args
460 #f))
461 (sieve-verbose-print "IMPLICIT KEEP")))
462
463 (define (sieve-run thunk)
464 (if (not sieve-my-email)
465 (set! sieve-my-email (mu-username->email)))
466 ; (DEBUG 1 "Mailbox: " sieve-mailbox)
467
468 (let msg-loop ((msg (mu-mailbox-first-message sieve-mailbox)))
469 (if (not (eof-object? msg))
470 (begin
471 (set! sieve-current-message msg)
472 (sieve-run-current-message thunk)
473 (msg-loop (mu-mailbox-next-message sieve-mailbox)))))
474
475 (sieve-close-mailboxes))
476
477 (define (sieve-command-line)
478 (catch #t
479 (lambda ()
480 (let ((args sieve-script-args))
481 (append (list "<temp-file>") args)))
482 (lambda args (command-line))))
483
484 (define-public (sieve-main thunk)
485 (handle-exception
486 (let* ((cl (sieve-command-line))
487 (name (if (and (not (null? (cdr cl)))
488 (string? (cadr cl)))
489 (cadr cl)
490 (mu-user-mailbox-url
491 (passwd:name (mu-getpwuid (getuid)))))))
492
493 (set! sieve-mailbox (mu-mailbox-open name "rw"))
494 (sieve-run thunk)
495 (mu-mailbox-expunge sieve-mailbox)
496 (mu-mailbox-close sieve-mailbox))))
1 #! %GUILE_BINDIR%/guile -s
2 # Emacs, it's -*- scheme -*-
3 !#
4 ;;;; GNU Mailutils -- a suite of utilities for electronic mail
5 ;;;; Copyright (C) 1999-2001, 2006-2007, 2009-2012, 2014-2017 Free
6 ;;;; Software Foundation, Inc.
7 ;;;;
8 ;;;; GNU Mailutils is free software; you can redistribute it and/or modify
9 ;;;; it under the terms of the GNU General Public License as published by
10 ;;;; the Free Software Foundation; either version 3, or (at your option)
11 ;;;; any later version.
12 ;;;;
13 ;;;; GNU Mailutils is distributed in the hope that it will be useful,
14 ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
15 ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 ;;;; GNU General Public License for more details.
17 ;;;;
18 ;;;; You should have received a copy of the GNU General Public License along
19 ;;;; with GNU Mailutils. If not, see <http://www.gnu.org/licenses/>.
20 ;;;;
21
22 ;;;; This is a Sieve to Scheme translator.
23 ;;;;
24 ;;;; To convert a sieve script into equivalent Scheme program, run:
25 ;;;;
26 ;;;; guile -s sieve2scm.scm --file <sieve-script-name> --output <output-file-name>
27 ;;;;
28 ;;;; To compile and execute a sieve script upon a mailbox, run:
29 ;;;;
30 ;;;; guile -s sieve2scm.scm --file <sieve-script-name> [mailbox-name]
31 ;;;;
32 (if (not (member "%GUILE_SITE%" %load-path))
33 (set! %load-path (cons "%GUILE_SITE%" %load-path)))
34 (use-modules (ice-9 getopt-long)
35 (ice-9 rdelim)
36 (mailutils sieve-core))
37
38 (set! sieve-parser #t)
39
40 (define sieve-debug 0)
41 (define sieve-libdir "%LIBDIR%")
42 (define sieve-load-files '())
43 (define sieve-script-args '())
44 (define request-verbose #f)
45
46 (define error-count 0)
47 (define current-token #f)
48 (define putback-list '())
49 (define input-port #f)
50 (define input-file "")
51 (define input-line-number 0)
52 (define input-line "")
53 (define input-index 0)
54 (define input-length 0)
55 (define nesting-level 0)
56 (define recovery-line-number -1)
57 (define recovery-index -1)
58
59 (define (DEBUG level . rest)
60 (if (>= sieve-debug level)
61 (begin
62 (display "DEBUG(")
63 (display level)
64 (display "):")
65 (for-each (lambda (x)
66 (display x))
67 rest)
68 (newline))))
69
70 ;;;; Lexical scanner
71 (define (delimiter? c)
72 (or (member c (list #\[ #\] #\{ #\} #\, #\; #\( #\)))
73 (char-whitespace? c)))
74
75 (define (lex-error . rest)
76 (set! error-count (1+ error-count))
77 (with-output-to-port
78 (current-error-port)
79 (lambda ()
80 (display input-file)
81 (display ":")
82 (display input-line-number)
83 (display ": ")
84 (for-each (lambda (x)
85 (display x))
86 rest)
87 (newline)))
88 #t)
89
90 (define (syntax-error . rest)
91 (set! error-count (1+ error-count))
92 (with-output-to-port
93 (current-error-port)
94 (lambda ()
95 (display input-file)
96 (display ":")
97 (display input-line-number)
98 (display ": ")
99 (for-each (lambda (x)
100 (display x))
101 rest)
102 (newline)))
103 (throw 'syntax-error))
104
105 ;;; If current input position points to end of line or to a start of
106 ;;; # comment, return #f. Otherwise return cons whose car contains
107 ;;; token type and cdr contains token itself (string).
108 (define (next-token)
109 (let ((start (do ((i input-index (1+ i)))
110 ((or (>= i input-length)
111 (not (char-whitespace? (string-ref input-line i))))
112 i))))
113 ; (DEBUG 100 "START " start ": " (substring input-line start))
114 (if (< start input-length)
115 (let ((char (string-ref input-line start)))
116 (DEBUG 100 "CHAR " char)
117 (case char
118 ((#\#)
119 (set! input-index input-length)
120 #f)
121 ((#\[ #\] #\{ #\} #\( #\) #\, #\;)
122 (set! input-index (1+ start))
123 (cons 'delimiter char))
124 ((#\")
125 (let ((end (do ((end (1+ start) (1+ end)))
126 ((or (>= end input-length)
127 (char=? (string-ref input-line end) #\"))
128 end))))
129 (if (>= end input-length)
130 (lex-error "Unterminated string constant"))
131 (set! input-index (1+ end))
132 (cons 'string (substring input-line (1+ start) end))))
133 (else
134 (DEBUG 100 "MATCH else")
135 (cond
136 ((and (char=? (string-ref input-line start) #\/)
137 (< (1+ start) input-length)
138 (char=? (string-ref input-line (1+ start)) #\*))
139 (set! input-index (+ start 2))
140 (cons 'bracket-comment "/*"))
141 ((char-numeric? char)
142 (let* ((end (do ((end start (1+ end)))
143 ((or
144 (>= end (1- input-length))
145 (not (char-numeric?
146 (string-ref input-line end))))
147
148 (cond
149 ((char-numeric? (string-ref input-line end))
150 (1+ end))
151 (else
152 end)))))
153 (num (string->number (substring input-line start end)))
154 (q (if (< end input-length)
155 (string-ref input-line end)
156 #f))
157 (k 1))
158 (case q
159 ((#f) #f) ;; nothing
160 ((#\K)
161 (set! end (1+ end))
162 (set! k 1024))
163 ((#\M)
164 (set! end (1+ end))
165 (set! k 1048576))
166 ((#\G)
167 (set! end (1+ end))
168 (set! k 1073741824))
169 (else
170 (if (not (delimiter? q))
171 (lex-error "Unknown qualifier (" q ")"))))
172 (set! input-index end)
173 (cons 'number (* num k))))
174 (else
175 (let ((end (do ((end start (1+ end)))
176 ((or (>= end input-length)
177 (delimiter? (string-ref input-line end)))
178 end))))
179 (DEBUG 100 "END " end)
180 (set! input-index end)
181 (cond
182 ((char=? char #\:)
183 (cons 'tag (substring input-line (1+ start) end)))
184 (else
185 (cons 'identifier (substring input-line start end))))))))))
186 #f)))
187
188 (define (end-of-line?)
189 (do ((i input-index (1+ i)))
190 ((or (>= i input-length)
191 (not (char-whitespace? (string-ref input-line i))))
192 (>= i input-length))))
193
194 (define (read-input-line port)
195 (set! input-line (read-line port))
196 (if (not (eof-object? input-line))
197 (begin
198 (set! input-line-number (1+ input-line-number))
199 (set! input-length (string-length input-line))
200 (set! input-index 0)))
201 input-line)
202
203 (define (next-token-from-port port)
204 (let ((tok (or (next-token)
205 (begin
206 (DEBUG 100 "2nd")
207 (set! input-line (read-line port))
208 (if (not (eof-object? input-line))
209 (begin
210 (set! input-line-number (1+ input-line-number))
211 (set! input-length (string-length input-line))
212 (set! input-index 0)
213 (next-token))
214 input-line)))))
215 (cond
216 ((or (not tok) (eof-object? tok))
217 tok)
218 ((and (eq? (car tok) 'identifier)
219 (string=? (cdr tok) "text:")
220 (end-of-line?))
221 (let ((text "")
222 (string-start input-line-number))
223 (do ((line (read-line port) (read-line port)))
224 ((or (and
225 (eof-object? line)
226 (lex-error
227 "Unexpected end of file in multiline string started on line "
228 string-start)
229 (throw 'end-of-file))
230 (let ((len (string-length line)))
231 (and (> len 0)
232 (char=? (string-ref line 0) #\.)
233 (do ((i 1 (1+ i)))
234 ((or (>= i len)
235 (not
236 (char-whitespace?
237 (string-ref line i))))
238 (>= i len))))))
239 #f)
240 (set! input-line-number (1+ input-line-number))
241 (if (and (not (string-null? line))
242 (char=? (string-ref line 0) #\.)
243 (char=? (string-ref line 1) #\.))
244 (set! line (substring line 1)))
245 (set! text (string-append text "\n" line)))
246 (set! input-length 0)
247 (set! input-index 0)
248 (cons 'string text)))
249 ((eq? (car tok) 'bracket-comment)
250 (let ((comment-start input-line-number))
251 (set! input-length (- input-length input-index))
252 (if (> input-length 0)
253 (begin
254 (set! input-line
255 (substring input-line input-index input-length))
256 (set! input-index 0))
257 (read-input-line port))
258 (do ()
259 ((> input-index 0) #f)
260 (cond
261 ((eof-object? input-line)
262 (lex-error
263 "Unexpected end of file in comment started on line "
264 comment-start)
265 (throw 'end-of-file))
266 (else
267 (let ((t (string-index input-line #\*)))
268 (if (and t
269 (< (1+ t) input-length)
270 (char=? (string-ref input-line (1+ t)) #\/))
271 (set! input-index (+ t 2))
272 (read-input-line port))))))))
273 (else
274 tok))))
275
276 (define (delimiter token c)
277 (and (pair? token)
278 (eq? (car token) 'delimiter)
279 (char=? (cdr token) c)))
280
281 (define (identifier token c)
282 (and (eq? (car token) 'identifier)
283 (string=? (cdr token) c)))
284
285 (define (putback-token)
286 (set! putback-list (append (list current-token)
287 putback-list)))
288
289 (define (read-token)
290 (cond
291 ((not (null? putback-list))
292 (set! current-token (car putback-list))
293 (set! putback-list (cdr putback-list)))
294 (else
295 (set! current-token (do ((token (next-token-from-port input-port)
296 (next-token-from-port input-port)))
297 (token token)))))
298 current-token)
299
300 (define (require-semicolon . read)
301 (if (null? read)
302 (read-token))
303 (if (or (eof-object? current-token)
304 (not (delimiter current-token #\;)))
305 (syntax-error "Missing ;")
306 current-token))
307
308 (define (require-tag . read)
309 (if (null? read)
310 (read-token))
311 (cond
312 ((eof-object? current-token)
313 (syntax-error "Expected tag but found " current-token))
314 ((not (eq? (car current-token) 'tag))
315 (syntax-error "Expected tag but found " (car current-token))))
316 current-token)
317
318 (define (require-string . read)
319 (if (null? read)
320 (read-token))
321 (cond
322 ((eof-object? current-token)
323 (syntax-error "Expected string but found " current-token))
324 ((not (eq? (car current-token) 'string))
325 (syntax-error "Expected string but found " (car current-token))))
326 current-token)
327
328 (define (require-number . read)
329 (if (null? read)
330 (read-token))
331 (cond
332 ((eof-object? current-token)
333 (syntax-error "Expected number but found " current-token))
334 ((not (eq? (car current-token) 'number))
335 (syntax-error "Expected number but found " (car current-token))))
336 current-token)
337
338 (define (require-string-list . read)
339 (if (null? read)
340 (read-token))
341 (cond
342 ((eof-object? current-token)
343 (syntax-error "Expected string-list but found " current-token))
344 ((eq? (car current-token) 'string)
345 (list 'string-list (cdr current-token)))
346 ((not (eq? (car current-token) 'delimiter))
347 (syntax-error "Expected string-list but found " (car current-token)))
348 ((char=? (cdr current-token) #\[)
349 (do ((slist '())
350 (token (read-token) (read-token)))
351 ((if (not (eq? (car token) 'string))
352 (begin
353 (syntax-error "Expected string but found " (car token))
354 #t)
355 (begin
356 (set! slist (append slist (list (cdr token))))
357 (read-token)
358 (cond
359 ((eof-object? current-token)
360 (syntax-error "Unexpected end of file in string list")
361 #t) ;; break;
362 ((eq? (car current-token) 'delimiter)
363 (cond
364 ((char=? (cdr current-token) #\,) #f) ;; continue
365 ((char=? (cdr current-token) #\]) #t) ;; break
366 (else
367 (lex-error "Expected ',' or ']' but found "
368 (cdr current-token))
369 #t)))
370 (else
371 (lex-error "Expected delimiter but found "
372 (car current-token))
373 #t))))
374 (cons 'string-list slist))))
375 (else
376 (syntax-error "Expected '[' but found " (car current-token)))))
377
378 (define (require-identifier . read)
379 (if (null? read)
380 (read-token))
381 (cond
382 ((eof-object? current-token)
383 (syntax-error "1. Expected identifier but found " current-token))
384 ((not (eq? (car current-token) 'identifier))
385 (syntax-error "2. Expected identifier but found " (car current-token))))
386 current-token)
387
388 ;;;;
389
390 ;;; Syntax tables.
391 ;;; A syntax table is a list of
392 ;;; Each entry is: (list NAME FUNCTION OPT-ARG-LIST REQ-ARG-LIST)
393 ;;; NAME is a string representing the input language keyword,
394 ;;; FUNCTION is a corresponding function:
395 ;;; (define (foo [arg [arg...]] . opt-args)
396 ;;; OPT-ARG-LIST is a list of optional arguments (tags),
397 ;;; REQ-ARG-LIST is a list of required (positional) arguments
398
399 (define (sieve-syntax-table-lookup table name)
400 (let ((entry (assoc name table)))
401 (if entry
402 (cdr entry)
403 #f)))
404
405 (define-macro (sieve-syntax-table-add table name function req-arg-list opt-arg-list)
406 `(cond
407 ((not (list? ,opt-arg-list))
408 (lex-error "sieve-syntax-table-add: opt-arg-list must be a list"))
409 ((not (list? ,req-arg-list))
410 (lex-error "sieve-syntax-table-add: req-arg-list must be a list"))
411 ((not (or (eq? ,function #f)
412 (eq? ,function #t)
413 (procedure? ,function)))
414 (lex-error "sieve-syntax-table-add: bad type for function " ,function))
415 (else
416 (set! ,table
417 (append ,table
418 (list
419 (list ,name ,function ,opt-arg-list ,req-arg-list)))))))
420
421 ;;;;
422
423 (defmacro do-for-all (fun rest)
424 `(for-each
425 (lambda (x)
426 (apply ,fun x))
427 ,rest))
428
429
430 ;;;; Available syntax tables.
431
432 ;;;; Comparators
433
434 ;;; Syntax table for comparators. The opt-arg-list and req-arg-list have
435 ;;; no meaning for comparators, so they are ignored. The handler function
436 ;;; names must start with "comparator-"
437 (define sieve-comparator-table '())
438
439 (define (sieve-find-comparator name)
440 (sieve-syntax-table-lookup sieve-comparator-table name))
441
442 (define (sieve-register-comparator name function)
443 (sieve-syntax-table-add sieve-comparator-table name function '() '()))
444
445 ;;; Register standard comparators
446 (do-for-all sieve-register-comparator sieve-standard-comparators)
447
448 ;;;; Sieve Tests
449
450 ;;; Syntax table for tests. Function names must start with "test-"
451 (define sieve-test-table '())
452
453 (define (sieve-find-test name)
454 (sieve-syntax-table-lookup sieve-test-table name))
455
456 (define (sieve-register-test name function req-arg-list opt-arg-list)
457 (sieve-syntax-table-add sieve-test-table name function
458 req-arg-list opt-arg-list))
459
460 ;;; Register standard tests
461 (do-for-all sieve-register-test sieve-standard-tests)
462
463 ;;;; Sieve Actions
464
465 ;;; Syntax table for actions. Function names start with "action-"
466 (define sieve-action-table '())
467
468 (define (sieve-find-action name)
469 (sieve-syntax-table-lookup sieve-action-table name))
470
471 (define (sieve-register-action name function req-arg-list opt-arg-list)
472 (sieve-syntax-table-add sieve-action-table name function
473 req-arg-list opt-arg-list))
474
475 ;;; Register standard actions
476 (do-for-all sieve-register-action sieve-standard-actions)
477
478 ;;;;
479
480 ;;;; Command parsers
481
482 ;;; sieve-preprocess-arguments: Preprocess and group the arguments. Returns
483 ;;; a cons whose car is a list of all optional arguments, and the cdr is
484 ;;; a list of the rest of the arguments.
485 ;;;
486 ;;; arguments = *argument [test / test-list]
487 ;;; argument = string-list / number / tag
488
489 (define (sieve-preprocess-arguments tag-gram)
490 (do ((opt-list '()) ;; List of optional arguments (tags)
491 (arg-list '()) ;; List of positional arguments
492 (last-tag #f) ;; Description of the last tag from tag-gram
493 (state 'opt) ;; 'opt when scanning optional arguments,
494 ;; 'arg when scanning positional arguments
495 (token (read-token) (read-token))) ;; Obtain next token
496 ((cond
497 ((eof-object? token)
498 (syntax-error "Expected argument but found " token))
499 ((eq? (car token) 'tag)
500 (if (not (eq? state 'opt))
501 (syntax-error "Misplaced tag: :" (cdr token)))
502 (set! last-tag (assoc (cdr token) tag-gram))
503 (if (not last-tag)
504 (syntax-error
505 "Tag :" (cdr token) " is not allowed in this context"))
506 (set! opt-list (append opt-list (list token)))
507 #f)
508 ((or (eq? (car token) 'number)
509 (eq? (car token) 'string))
510 (cond
511 ((and (eq? state 'opt) (pair? last-tag))
512 (cond
513 ((cdr last-tag)
514 (if (not (eq? (cdr last-tag) (car token)))
515 (syntax-error
516 "Tag :" (car last-tag) " takes " (cdr last-tag) " argument"))
517 (cond
518 ((string=? (car last-tag) "comparator")
519 (let ((comp (sieve-find-comparator (cdr token))))
520 (if (not comp)
521 (syntax-error "Undefined comparator: " (cdr token)))
522 (set-cdr! token (car comp)))))
523 (set! opt-list (append opt-list (list token)))
524 (set! last-tag #f))
525 (else
526 (set! state 'arg)
527 (set! arg-list (append arg-list (list token))))))
528 (else
529 (set! arg-list (append arg-list (list token)))))
530 #f)
531 ((delimiter token #\[)
532 (putback-token)
533 (cond
534 ((and (eq? state 'opt) (pair? last-tag))
535 (cond
536 ((cdr last-tag)
537 (if (not (eq? (cdr last-tag) 'string-list))
538 (syntax-error
539 "Tag :" (car last-tag) " takes string list argument"))
540 (set! opt-list (append opt-list (list (require-string-list))))
541 (set! last-tag #f))
542 (else
543 (set! state 'arg)
544 (set! arg-list (append arg-list (list (require-string-list)))))))
545 (else
546 (set! arg-list (append arg-list (list (require-string-list))))))
547 #f)
548 (else
549 #t))
550 (cons opt-list arg-list))))
551
552 ;;; sieve-parse-arguments: Parse the arguments to a test or an action.
553 ;;; ENTRY is the syntax table entry to guide the parsing
554 ;;;
555 (define (sieve-parse-arguments ident entry)
556 (DEBUG 100 "sieve-parse-arguments" entry)
557 (let ((arg-list (sieve-preprocess-arguments (car (cdr entry)))))
558 ;; Process positional arguments
559 (do ((expect (car (cdr (cdr entry))) (cdr expect))
560 (argl (cdr arg-list) (cdr argl))
561 (n 1 (1+ n)))
562 ((cond
563 ((null? expect)
564 (if (not (null? argl))
565 (syntax-error
566 "Too many positional arguments for " ident
567 " (bailed out at " (car argl) ")"))
568 #t)
569 ((null? argl)
570 (if (not (null? expect))
571 (syntax-error
572 "Too few positional arguments for " ident))
573 #t)
574 (else #f)) #f)
575 (let ((expect-type (car expect))
576 (arg (car argl)))
577 (cond
578 ((and (eq? expect-type 'string-list)
579 (eq? (car arg) 'string))
580 ;; Coerce string to string-list
581 (sieve-exp-append (list 'list (cdr arg))))
582 ((eq? expect-type (car arg))
583 (if (eq? expect-type 'string-list)
584 (sieve-exp-append (append (list 'list) (cdr arg)))
585 (sieve-exp-append (cdr arg))))
586 (else
587 (syntax-error
588 "Type mismatch in argument " n " to " (cdr ident)
589 "; expected " expect-type ", but got " (car arg))))))
590 ;; Process optional arguments (tags).
591 ;; They have already been tested
592 (for-each
593 (lambda (tag)
594 (sieve-exp-append (cond
595 ((eq? (car tag) 'tag)
596 (symbol->keyword
597 (string->symbol (cdr tag))))
598 ((eq? (car tag) 'string-list)
599 (append (list 'list) (cdr tag)))
600 (else
601 (cdr tag)))))
602 (car arg-list))))
603
604 ;;;;
605
606 ;;;; Parser functions for tests
607
608 ;;; test-list = "(" test *("," test) ")"
609 (define (sieve-parse-test-list)
610 (do ((token (sieve-parse-test) (sieve-parse-test)))
611 ((cond
612 ((delimiter token #\))
613 #t) ;; break;
614 ((delimiter token #\,)
615 #f) ;; continue
616 ((eof-object? token)
617 (syntax-error "Unexpected end of file in test-list")
618 #t) ;; break
619 (else
620 (syntax-error "Expected ',' or ')' but found " (cdr token))
621 #t)) ;; break
622 (read-token))))
623
624 ;;; test = identifier arguments
625 (define (sieve-parse-test)
626 (let ((ident (require-identifier)))
627 (cond
628 ((string=? (cdr ident) "not")
629 (sieve-exp-begin)
630 (sieve-exp-append 'not)
631 (sieve-parse-test)
632 (sieve-exp-finish))
633 (else
634 (read-token)
635 (cond
636 ((eof-object? current-token)
637 (syntax-error "Unexpected end of file in conditional"))
638 ((delimiter current-token #\()
639 (sieve-exp-begin)
640 (cond
641 ((string=? (cdr ident) "allof")
642 (sieve-exp-append 'and))
643 ((string=? (cdr ident) "anyof")
644 (sieve-exp-append 'or))
645 (else
646 (syntax-error "Unexpected '('")))
647 (sieve-parse-test-list)
648 (sieve-exp-finish))
649 (else
650 (let ((test (sieve-find-test (cdr ident))))
651 (if (not test)
652 (syntax-error "Unknown test name: " (cdr ident)))
653 (cond
654 ((procedure? (car test))
655 (putback-token)
656 (sieve-exp-begin)
657 (sieve-exp-append (car test))
658 (sieve-parse-arguments (cdr ident) test)
659 (sieve-exp-finish))
660 (else
661 (sieve-exp-append (car test))))))))))
662 current-token)
663
664 (define (sieve-parse-block . read)
665 (if (not (null? read))
666 (read-token))
667 (if (delimiter current-token #\{)
668 (begin
669 (set! nesting-level (1+ nesting-level))
670 (do ((token (read-token) (read-token)))
671 ((cond
672 ((eof-object? token)
673 (syntax-error "Unexpected end of file in block")
674 #t)
675 ((delimiter token #\})
676 #t)
677 (else
678 (putback-token)
679 (sieve-parse-command)
680 #f))) #f)
681 (set! nesting-level (1- nesting-level)))
682 (require-semicolon 'dont-read)))
683
684 ;;; if <test1: test> <block1: block>
685 (define (sieve-parse-if-internal)
686 (DEBUG 10 "sieve-parse-if-internal" current-token)
687 (sieve-exp-begin)
688
689 (sieve-parse-test)
690
691 (sieve-parse-block)
692 (sieve-exp-finish)
693
694 (read-token)
695 (cond
696 ((eof-object? current-token) )
697 ((identifier current-token "elsif")
698 (sieve-parse-if-internal))
699 ((identifier current-token "else")
700 (sieve-exp-begin 'else)
701 (sieve-parse-block 'read)
702 (sieve-exp-finish))
703 (else
704 (putback-token))))
705
706 (define (sieve-parse-if)
707 (sieve-exp-begin 'cond)
708 (sieve-parse-if-internal)
709 (sieve-exp-finish))
710
711 (define (sieve-parse-else)
712 (syntax-error "else without if"))
713
714 (define (sieve-parse-elsif)
715 (syntax-error "elsif without if"))
716
717 ;;; require <capabilities: string-list>
718 (define (sieve-parse-require)
719 (for-each
720 (lambda (capability)
721 (if (not
722 (cond
723 ((and
724 (>= (string-length capability) 5)
725 (string=? (substring capability 0 5) "test-"))
726 (sieve-find-test (substring capability 5)))
727 ((and
728 (>= (string-length capability) 11)
729 (string=? (substring capability 0 11) "comparator-"))
730 (sieve-find-comparator (substring capability 11)))
731 (else
732 (sieve-find-action capability))))
733 (let ((name (string-append sieve-libdir
734 "/" capability ".scm")))
735 (set! sieve-load-files (append sieve-load-files (list name)))
736 (catch #t
737 (lambda ()
738 (load name))
739 (lambda args
740 (lex-error "Can't load required capability "
741 capability)
742 args)))))
743 (cdr (require-string-list)))
744 (require-semicolon))
745
746 ;;; stop
747 (define (sieve-parse-stop)
748 (sieve-exp-begin sieve-stop)
749 (sieve-exp-finish)
750 (require-semicolon))
751
752 ;;;;
753
754 ;;;; Parser functions for actions
755
756 (define (sieve-parse-action)
757 (let* ((name (cdr current-token))
758 (descr (sieve-find-action name)))
759 (cond
760 (descr
761 (cond
762 ((car descr)
763 (sieve-exp-begin 'reg-action)
764 (sieve-exp-finish)
765 (sieve-exp-begin (car descr))
766 (sieve-parse-arguments name descr)
767 (require-semicolon 'dont-read)
768 (sieve-exp-finish))
769 (else
770 (require-semicolon))))
771 (else
772 (syntax-error "Unknown identifier: " name)))))
773
774 ;;;;
775
776 ;;;; The parser
777
778 (define (sieve-parse-command)
779 (DEBUG 10 "sieve-parse-command" current-token)
780 (catch 'syntax-error
781 (lambda ()
782 (read-token)
783 (cond
784 ((or (not current-token)
785 (eof-object? current-token))) ;; Skip comments and #<eof>
786 ((eq? (car current-token) 'identifier)
787 ;; Process a command
788 (let ((elt (assoc (string->symbol (cdr current-token))
789 (list
790 (cons 'if sieve-parse-if)
791 (cons 'elsif sieve-parse-elsif)
792 (cons 'else sieve-parse-else)
793 (cons 'require sieve-parse-require)
794 (cons 'stop sieve-parse-stop)))))
795 (if (not elt)
796 (sieve-parse-action)
797 (apply (cdr elt) '()))))
798 (else
799 (syntax-error "3. Expected identifier but found "
800 (cdr current-token)))))
801 (lambda args
802 ;; Error recovery: skip until we find a ';' or '}'.
803 (if (and (= input-line-number recovery-line-number)
804 (= input-index recovery-index))
805 (begin
806 (lex-error "ERROR RECOVERY: Skipping to end of file")
807 (throw 'end-of-file)))
808 (set! recovery-line-number input-line-number)
809 (set! recovery-index input-index)
810
811 (if (or (delimiter current-token #\})
812 (delimiter current-token #\;))
813 (read-token))
814 (DEBUG 50 "ERROR RECOVERY at " current-token)
815 (do ((token current-token (read-token)))
816 ((cond
817 ((eof-object? token)
818 (throw 'end-of-file))
819 ((delimiter token #\;)
820 #t)
821 ((delimiter token #\})
822 (cond
823 ((> nesting-level 0)
824 (putback-token)
825 #t)
826 (else
827 #f)))
828 ((delimiter token #\{)
829 (sieve-skip-block)
830 (putback-token)
831 #f)
832 (else
833 #f)) #f))
834 (DEBUG 50 "ERROR RECOVERY FINISHED AT " current-token)))
835 current-token)
836
837 (define (sieve-skip-block)
838 (do ((token (read-token) (read-token)))
839 ((cond
840 ((eof-object? token)
841 (throw 'end-of-file))
842 ((delimiter token #\{)
843 (sieve-skip-block)
844 #f)
845 ((delimiter token #\})
846 #t)
847 (else
848 #f)) #f)))
849
850 (define (sieve-parse-from-port port)
851 (set! input-port port)
852 (do ((token (sieve-parse-command) (sieve-parse-command)))
853 ((eof-object? token) #f)) )
854
855 (define (sieve-parse filename)
856 (if (file-exists? filename)
857 (catch 'end-of-file
858 (lambda ()
859 (set! error-count 0)
860 (set! current-token #f)
861 (set! input-file filename)
862 (set! input-line-number 0)
863 (set! putback-list '())
864 (call-with-input-file filename sieve-parse-from-port))
865 (lambda args args))))
866
867 ;;;;
868
869 ;;;; Code generator
870
871 (define sieve-exp '()) ;; Expression currently being built
872 (define sieve-exp-stack '())
873 (define sieve-code-list '()) ;; Resulting scheme code
874
875 (define (sieve-exp-begin . exp)
876 (set! sieve-exp-stack (append (list sieve-exp) sieve-exp-stack))
877 (set! sieve-exp exp))
878
879 (define (sieve-exp-append exp)
880 (set! sieve-exp (append sieve-exp (list exp))))
881
882 (define (sieve-exp-finish)
883 (set! sieve-exp (append (car sieve-exp-stack) (list sieve-exp)))
884 (set! sieve-exp-stack (cdr sieve-exp-stack)))
885
886 (define (sieve-code-begin)
887 (set! sieve-exp-stack '())
888 (set! sieve-exp '()))
889
890 (define (sieve-code-prologue code-list)
891 (sieve-exp-begin (car code-list))
892 (let loop ((code-list (cdr code-list)))
893 (for-each
894 (lambda (elt)
895 (cond
896 ((and (list? elt) (not (null? elt)))
897 (sieve-exp-begin (car elt))
898 (loop (cdr elt))
899 (sieve-exp-finish))
900 (else
901 (sieve-exp-append elt))))
902 code-list)))
903
904 (define (sieve-code-finish)
905 (if (not (null? sieve-exp))
906 (set! sieve-code-list (append sieve-code-list sieve-exp))))
907
908 ;;; Print the program
909
910 (define (sieve-code-print-list exp)
911 (display "(")
912 (for-each
913 (lambda (x)
914 (cond
915 ((procedure? x)
916 (display (procedure-name x)))
917 ((list? x)
918 (sieve-code-print-list x))
919 (else
920 (write x)))
921 (display " "))
922 exp)
923 (display ")"))
924
925 ;;; Save the program
926
927 (define (sieve-save-program outfile)
928 (with-output-to-file
929 outfile
930 (lambda ()
931 (display "#! ")
932 (display "/bin/sh\n\
933 # aside from this initial boilerplate, this is actually -*- scheme -*- code\n\
934 exec ${GUILE-guile} -l $0 -c '(mailutils-main)'\n")
935 (display (string-append
936 "# This Guile mailbox parser was made from " filename))
937 (newline)
938 (display "# by sieve.scm, GNU %PACKAGE% %VERSION%\n!#")
939 (newline)
940
941 (display
942 "(if (not (member \"%GUILE_SITE%\" %load-path))\n
943 (set! %load-path (cons \"%GUILE_SITE%\" %load-path)))\n")
944
945 (display "(use-modules (mailutils sieve-core))\n")
946 (display (string-append
947 "(set! sieve-source \"" filename "\")"))
948 (newline)
949
950 (for-each
951 (lambda (file)
952 (display (string-append
953 "(load \"" file "\")"))
954 (newline))
955 sieve-load-files)
956 (newline)
957 (if request-verbose
958 (display "(set! sieve-verbose #t)\n"))
959 (display "(define (sieve-filter-thunk) ")
960
961 (sieve-code-print-list (car sieve-code-list))
962 (display ")\n\n")
963
964 (display "(define (mailutils-main . rest)\n")
965 (display " (sieve-main sieve-filter-thunk))\n\n")
966
967 (display "(define (mailutils-check-message msg)\n\
968 (set! sieve-current-message msg)\n\
969 (sieve-run-current-message sieve-filter-thunk))\n")
970
971 (display "\n\
972 ;;;; Local Variables:\n\
973 ;;;; buffer-read-only: t\n\
974 ;;;; End:\n"))))
975
976 ;;;;
977
978 ;;;; Main
979
980 (define filename #f)
981 (define output #f)
982
983 (define (sieve-usage)
984 (display "usage: sieve2scm [OPTIONS] [mailbox]\n")
985 (display "GNU sieve2scm -- compile a Sieve program into Scheme code\n\n")
986 (display " -f, --file FILENAME Set input file name\n")
987 (display " -o, --output FILENAME Set output file name\n")
988 (display " -L, --lib-dir DIRNAME Set sieve library directory name\n")
989 (display " -d, --debug LEVEL Set debugging level\n")
990 (display " --version Show program version\n\n")
991 (display "If -o option is not given, the compiled program is executed\n")
992 (display "immediately. It operates on the user system mailbox unless\n")
993 (display "mailbox is given in the command line.\n")
994 (exit 0))
995
996 (define (sieve-version)
997 (format #t "sieve2scm (~A) ~A~%" mu-package mu-version)
998 (exit 0))
999
1000 ;;; Parse command line
1001
1002 (define grammar
1003 `((file (single-char #\f)
1004 (value #t))
1005 (output (single-char #\o)
1006 (value #t))
1007 (debug (single-char #\d)
1008 (value #t))
1009 (lib-dir (single-char #\L)
1010 (value #t))
1011 (version)
1012 (verbose (single-char #\v))
1013 (help (single-char #\h))))
1014
1015 (define program-name (car (command-line)))
1016
1017 (for-each
1018 (lambda (x)
1019 (cond
1020 ((pair? x)
1021 (case (car x)
1022 ((debug)
1023 (set! sieve-debug (string->number (cdr x))))
1024 ((file)
1025 (set! filename (cdr x)))
1026 ((lib-dir)
1027 (set! sieve-libdir (cdr x)))
1028 ((output)
1029 (set! output (cdr x)))
1030 ((version)
1031 (sieve-version))
1032 ((verbose)
1033 (set! request-verbose #t))
1034 ((help)
1035 (sieve-usage))
1036 ('()
1037 (set! sieve-script-args (cdr x)))))))
1038 (getopt-long (command-line) grammar))
1039
1040 (cond
1041 ((not filename)
1042 (format (current-error-port) "~A: missing input filename~%" program-name)
1043 (sieve-usage))
1044 ((not (file-exists? filename))
1045 (format (current-error-port) "~A: Input file ~A does not exist~%" filename)
1046 (exit 0)))
1047
1048 (if (not sieve-libdir)
1049 (set! sieve-libdir
1050 (let ((myname (car (command-line))))
1051 (if (not (char=? (string-ref myname 0) #\/))
1052 (set! myname (string-append (getcwd) "/" myname)))
1053 (let ((slash (string-rindex myname #\/)))
1054 (substring myname 0 slash)))))
1055
1056 (sieve-code-prologue
1057 '(letrec
1058 ((implicit-keep #t)
1059 (reg-action (lambda () (set! implicit-keep #f))))))
1060
1061 (sieve-parse filename)
1062 (sieve-exp-append 'implicit-keep)
1063 (sieve-exp-finish)
1064 (sieve-code-finish)
1065
1066 (cond
1067 ((> error-count 0)
1068 (display error-count)
1069 (display " errors.")
1070 (newline)
1071 (exit 1))
1072 (output
1073 (sieve-save-program output))
1074 (else
1075 (let ((temp-file (tmpnam))
1076 (saved-umask (umask #o077)))
1077 (sieve-save-program temp-file)
1078 (catch #t
1079 (lambda ()
1080 (set-cdr! (command-line) sieve-script-args)
1081 (load temp-file))
1082 (lambda (key . args)
1083 (apply display-error the-last-stack (current-error-port) args)))
1084 (delete-file temp-file)
1085 (umask saved-umask))))
1086
1087 ;;;; End of sieve.scm
1088
1089
1090
1 ;;;; GNU Mailutils -- a suite of utilities for electronic mail
2 ;;;; Copyright (C) 1999-2001, 2006-2007, 2010-2012, 2014-2017 Free
3 ;;;; Software Foundation, Inc.
4 ;;;;
5 ;;;; GNU Mailutils is free software; you can redistribute it and/or modify
6 ;;;; it under the terms of the GNU General Public License as published by
7 ;;;; the Free Software Foundation; either version 3, or (at your option)
8 ;;;; any later version.
9 ;;;;
10 ;;;; GNU Mailutils is distributed in the hope that it will be useful,
11 ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 ;;;; GNU General Public License for more details.
14 ;;;;
15 ;;;; You should have received a copy of the GNU General Public License along
16 ;;;; with GNU Mailutils. If not, see <http://www.gnu.org/licenses/>.
17
18 ;;;; This module provides "vacation" extension
19
20 ;;; vacation example:
21 ;;; vacation :days 18
22 ;;; :aliases ["gray@gnu.org", "gray@mirddin.farlep.net"]
23 ;;; :addresses ["bug-mailutils@gnu.org","bug-inetutils@gnu.org"]
24 ;;; :subject "I'm on vacation"
25 ;;; :mime
26 ;;; text:
27 ;;; I am on vacation until July 22. I'll attend your message as soon
28 ;;; as I'm back.
29 ;;; .
30 ;;;
31 ;;; Additionally, the :sender flag may be used to debug the script.
32
33 ;; Debugging flag
34 (define vacation-debug #f)
35
36 ;; Each entry is (cons SENDER DATE), where SENDER is the sender email
37 ;; address (lowercase) and DATE is the date where the first message
38 ;; from this sender was received.
39 (define vacation-db '())
40
41 (define (vacation-downcase name)
42 (let ((len (string-length name)))
43 (do ((i 0 (1+ i)))
44 ((= i len) name)
45 (string-set! name i (char-downcase (string-ref name i))))))
46
47 (define (vacation-db-name)
48 (let ((pwd (mu-getpwuid (getuid))))
49 (string-append (vector-ref pwd 5) "/.vacation.db")))
50
51 (define (vacation-db-load)
52 (catch #t
53 (lambda ()
54 (call-with-input-file (vacation-db-name)
55 (lambda (port)
56 (set! vacation-db (read port)))))
57 (lambda args args)))
58
59 (define (vacation-db-save)
60 (catch #t
61 (lambda ()
62 (let ((mask (umask #o077)))
63 (call-with-output-file (vacation-db-name)
64 (lambda (port)
65 (display ";; Vacation database file\n" port)
66 (display ";; Generated automatically. Please do not edit\n"
67 port)
68 (write vacation-db port)))
69 (umask mask)))
70 (lambda args args)))
71
72 (define (vacation-db-lookup sender days)
73 (vacation-db-load)
74 (let ((val (assoc (vacation-downcase sender) vacation-db)))
75 (cond
76 (val
77 (cond
78 ((and days (> days 0))
79 (<= (- (car (gettimeofday)) (cdr val)) (* days 86400)))
80 (else
81 #t)))
82 (else
83 #f))))
84
85 (define (vacation-db-update msg)
86 (let* ((sender (vacation-downcase (mu-message-get-sender msg)))
87 (date (car (gettimeofday)))
88 (val (assoc sender vacation-db)))
89 (cond
90 (val
91 (set-cdr! val date))
92 (else
93 (set! vacation-db (append vacation-db (list
94 (cons sender date)))))))
95 (vacation-db-save))
96
97 (define vacation-noreply-senders
98 (list
99 ".*-REQUEST@.*"
100 ".*-RELAY@.*"
101 ".*-OWNER@.*"
102 "OWNER-.*"
103 "postmaster@.*"
104 "UUCP@.*"
105 "MAILER@.*"
106 "MAILER-DAEMON@.*"))
107
108 (define (vacation-reply? msg aliases addresses days)
109 (let ((sender (mu-message-get-sender msg)))
110 (and
111 ;; No message will be sent unless an alias is part of either
112 ;; the "To:" or "Cc:" headers of the mail.
113 (call-with-current-continuation
114 (lambda (exit)
115 (for-each
116 (lambda (hdr)
117 (cond
118 (hdr
119 (let ((count (mu-address-get-count hdr)))
120 (do ((i 1 (1+ i)))
121 ((> i count) #f)
122 (let ((email (mu-address-get-email hdr i)))
123 (for-each
124 (lambda (alias)
125 (if (string-ci=? alias email)
126 (exit #t)))
127 aliases)))))))
128 (list (mu-message-get-header msg "To")
129 (mu-message-get-header msg "Cc")))
130 #f))
131
132 ;; Messages sent from one of the vacation-noreply-senders are not
133 ;; responded to
134 (call-with-current-continuation
135 (lambda (exit)
136 (do ((explist (append vacation-noreply-senders addresses)
137 (cdr explist)))
138 ((null? explist) #t)
139 (let ((rx (make-regexp (car explist) regexp/icase)))
140 (if (regexp-exec rx sender)
141 (exit #f))))))
142
143 ;; Messages with Precedence: bulk or junk are not responded to
144 (let ((prec (mu-message-get-header msg "Precedence")))
145 (not (and prec (or (string-ci=? prec "bulk")
146 (string-ci=? prec "junk")))))
147
148 ;; Senders already in the database get no response
149 (not (vacation-db-lookup sender days)))))
150
151 (define (vacation-send-reply subject text sender)
152 (let ((sender "root@localhost")
153 (mesg (mu-message-create)))
154 (let ((port (mu-message-get-port mesg "w")))
155 (display text port)
156 (close-output-port port))
157 (mu-message-set-header mesg "X-Sender"
158 (string-append "vacation.scm, " mu-package-string)
159 #t)
160 (mu-message-send mesg #f #f sender)))
161
162 (define (action-vacation text . opt)
163 (sieve-verbose-print "VACATION")
164 (set! vacation-debug (member #:debug opt))
165 (if vacation-debug
166 (begin
167 (display sieve-current-message)(display ": ")))
168 (cond
169 ((vacation-reply? sieve-current-message
170 (append (list sieve-my-email)
171 (sieve-get-opt-arg opt #:aliases '()))
172 (sieve-get-opt-arg opt #:addresses '())
173 (sieve-get-opt-arg opt #:days #f))
174 (vacation-send-reply (sieve-get-opt-arg
175 opt #:subject
176 (string-append "Re: "
177 (mu-message-get-header
178 sieve-current-message
179 "Subject")))
180 text
181 (sieve-get-opt-arg
182 opt #:sender
183 (mu-message-get-sender sieve-current-message)))
184 (vacation-db-update sieve-current-message)
185 (if vacation-debug
186 (display "WILL REPLY\n")))
187 (vacation-debug
188 (display "WILL NOT REPLY\n"))))
189
190 ;;; Register action
191 (if sieve-parser
192 (sieve-register-action "vacation"
193 action-vacation
194 (list 'string)
195 (list (cons "days" 'number)
196 (cons "addresses" 'string-list)
197 (cons "aliases" 'string-list)
198 (cons "subject" 'string)
199 (cons "sender" 'string)
200 (cons "mime" #f)
201 (cons "debug" #f))))
202